From b788865eef27fbbef7c4889879bd72de49ea6318 Mon Sep 17 00:00:00 2001 From: Tad Kollar Date: Wed, 6 Nov 2019 15:50:38 -0500 Subject: [PATCH 1/4] Computing whole matrix once, using offsets to find matrix cells --- .../visualization/n2_viewer/src/ModelData.js | 2 +- .../visualization/n2_viewer/src/N2Diagram.js | 10 +- .../visualization/n2_viewer/src/N2Matrix.js | 236 +++++++++++------- .../n2_viewer/src/N2MatrixCell.js | 182 +++++++++----- 4 files changed, 273 insertions(+), 157 deletions(-) diff --git a/openmdao/visualization/n2_viewer/src/ModelData.js b/openmdao/visualization/n2_viewer/src/ModelData.js index e00dcb4a78..9f42449fa6 100644 --- a/openmdao/visualization/n2_viewer/src/ModelData.js +++ b/openmdao/visualization/n2_viewer/src/ModelData.js @@ -40,7 +40,7 @@ class ModelData { this._computeConnections(); console.timeEnd('ModelData._computeConnections'); - // console.log("New model: ", modelJSON); + console.log("New model: ", modelJSON); // this.errorCheck(); } diff --git a/openmdao/visualization/n2_viewer/src/N2Diagram.js b/openmdao/visualization/n2_viewer/src/N2Diagram.js index 750585ebb3..7e0499a3bf 100644 --- a/openmdao/visualization/n2_viewer/src/N2Diagram.js +++ b/openmdao/visualization/n2_viewer/src/N2Diagram.js @@ -64,6 +64,8 @@ class N2Diagram { this.matrix = new N2Matrix(this.model, this.layout, this.dom.n2Groups, true, this.ui.findRootOfChangeFunction); + this.matrixMax = this.matrix; + // TODO: Move to N2Layout this.scales = { 'unit': 'px', @@ -147,11 +149,7 @@ class N2Diagram { */ updateZoomedElement(newZoomedElement) { this.zoomedElementPrev = this.zoomedElement; - - // TODO: Stop updating the global zoomedElement when we - // implement a different place to put it. this.zoomedElement = newZoomedElement; - this.layout.zoomedElement = this.zoomedElement; } @@ -555,9 +553,13 @@ class N2Diagram { this.layout = new N2Layout(this.model, this.zoomedElement, this.showLinearSolverNames, this.dims); this.ui.updateClickedIndices(); + /* this.matrix = new N2Matrix(this.model, this.layout, this.dom.n2Groups, this.ui.lastClickWasLeft, this.ui.findRootOfChangeFunction, this.matrix.nodeSize); + */ + this.matrix.update(this.layout, this.ui.lastClickWasLeft, + this.ui.findRootOfChangeFunction); } this._updateScale(); diff --git a/openmdao/visualization/n2_viewer/src/N2Matrix.js b/openmdao/visualization/n2_viewer/src/N2Matrix.js index fa7c6bcb85..3d9f7ff6fe 100644 --- a/openmdao/visualization/n2_viewer/src/N2Matrix.js +++ b/openmdao/visualization/n2_viewer/src/N2Matrix.js @@ -12,7 +12,7 @@ * @property {Object} cellDims nodeSize with computed coordinates. * @property {Object} prevCellDims prevNodeSize with computed coordinates. * @property {Object[][]} grid Object keys corresponding to rows and columns. - * @property {Array} allCells One-dimensional array of all cells, for D3 processing. + * @property {Array} visibleCells One-dimensional array of all cells, for D3 processing. * @property {Array} boxInfo Component box dimensions. */ class N2Matrix { @@ -24,8 +24,6 @@ class N2Matrix { * @param {Object} n2Groups References to SVG elements created by N2Diagram. * @param {Boolean} lastClickWasLeft * @param {function} findRootOfChangeFunction - * @param {Object} [prevNodeSize = {'width': 0, 'height': 0}] Previous node - * width & height for transition purposes. */ constructor(model, layout, n2Groups, lastClickWasLeft, findRootOfChangeFunction, @@ -37,36 +35,13 @@ class N2Matrix { this.lastClickWasLeft = lastClickWasLeft; this.findRootOfChangeFunction = findRootOfChangeFunction; - this.prevNodeSize = prevNodeSize; + this.prevNodeSize = { 'width': 0, 'height': 0 }; this.nodeSize = { 'width': layout.size.diagram.width / this.nodes.length, 'height': layout.size.diagram.height / this.nodes.length, } - this.cellDims = { - 'size': this.nodeSize, - 'bottomRight': { - 'x': this.nodeSize.width * .5, - 'y': this.nodeSize.height * .5 - }, - 'topLeft': { - 'x': -(this.nodeSize.width * .5), - 'y': -(this.nodeSize.width * .5) - } - } - - this.prevCellDims = { - 'size': this.prevNodeSize, - 'bottomRight': { - 'x': this.prevNodeSize.width * .5, - 'y': this.prevNodeSize.height * .5 - }, - 'topLeft': { - 'x': -(this.prevNodeSize.width * .5), - 'y': -(this.prevNodeSize.width * .5) - } - } - + N2CellRenderer.updateDims(this.nodeSize.width, this.nodeSize.height); this.updateLevelOfDetailThreshold(layout.size.diagram.height); console.time('N2Matrix._buildGrid'); @@ -78,6 +53,9 @@ class N2Matrix { console.timeEnd('N2Matrix._setupComponentBoxesAndGridLines'); } + get cellDims() { return N2CellRenderer.dims; } + get prevCellDims() { return N2CellRenderer.prevDims; } + /** * Determine if a node exists at the specified location. * @param {number} row Row number of the node @@ -85,8 +63,11 @@ class N2Matrix { * @returns False if the row doesn't exist or a column doesn't exist * in the row; true otherwise. */ - exists(row, col) { - if (this.grid[row] && this.grid[row][col]) { return true; } + exists(row, col, useOffset = true) { + let offsetRow = useOffset? row + N2MatrixCell.boundary.first.row : row, + offsetCol = useOffset? col + N2MatrixCell.boundary.first.col : col; + + if (this.grid[offsetRow] && this.grid[offsetRow][offsetCol]) { return true; } return false; } @@ -98,12 +79,15 @@ class N2Matrix { * @param {boolean} doThrow Whether to throw an exception if node undefined. * @returns {N2MatrixCell} The node if it exists, undefined otherwise. */ - cell(row, col, doThrow = false) { - if (this.exists(row, col)) { - return this.grid[row][col]; + cell(row, col, doThrow = false, useOffset = true) { + let offsetRow = useOffset? row + N2MatrixCell.boundary.first.row : row, + offsetCol = useOffset? col + N2MatrixCell.boundary.first.col : col; + + if (this.exists(row, col, useOffset)) { + return this.grid[offsetRow][offsetCol]; } else if (doThrow) { - throw "No node in matrix at (" + row + ", " + col + ")."; + throw "No node in matrix at (" + offsetRow + ", " + offsetCol + ")."; } return undefined; @@ -118,17 +102,21 @@ class N2Matrix { } tooMuchDetail() { - return (this.nodes.length >= this.levelOfDetailThreshold); + let tooMuch = (this.nodes.length >= this.levelOfDetailThreshold); + + if (tooMuch) console.log("Too much detail.") + + return tooMuch; } /** - * Add a cell to the grid Object and allCells array, with special + * Add a cell to the grid Object and visibleCells array, with special * handling for declared partials. - * @param {N2MatrixCell} newCell Cell created in _buildGrid(). * @param {number} row Index of the row in the grid to place the cell. * @param {number} col Index of the column in the grid to place the cell. + * @param {N2MatrixCell} newCell Cell created in _buildGrid(). */ - _addCell(newCell, row, col) { + _addCell(row, col, newCell) { if (modelData.options.use_declare_partial_info && newCell.symbolType.potentialDeclaredPartial && !newCell.symbolType.declaredPartial) { @@ -136,8 +124,8 @@ class N2Matrix { return; } - this.grid[row][col] = newCell; - this.allCells.push(newCell); + if (!this.exists(row, col)) { this.grid[row][col] = newCell; } + this.visibleCells.push(newCell); } /** @@ -145,31 +133,43 @@ class N2Matrix { * matrix, but not an actual two dimensional array because most of * it would be unused. */ - _buildGrid(model) { - this.grid = {}; - this.allCells = []; + _buildGrid(model = null) { + this.visibleCells = []; + let makeNewGrid = (this.grid == undefined); + + if (makeNewGrid) { + this.grid = {}; + } + else { + N2MatrixCell.updateBoundary(this.nodes[0].cell, + this.nodes[this.nodes.length - 1].cell); - if (this.tooMuchDetail()) return; + if (this.tooMuchDetail()) return; + } for (let srcIdx = 0; srcIdx < this.nodes.length; ++srcIdx) { let srcObj = this.nodes[srcIdx]; // New row - if (!this.exists(srcIdx)) this.grid[srcIdx] = {}; + if (!this.exists(srcIdx) && makeNewGrid) this.grid[srcIdx] = {}; // On the diagonal - let newCell = new N2MatrixCell(srcIdx, srcIdx, srcObj, srcObj, model, - this.cellDims, this.prevCellDims); - this._addCell(newCell, srcIdx, srcIdx); + let newCell = makeNewGrid ? + new N2MatrixCell(srcIdx, srcIdx, srcObj, srcObj, model, + this.cellDims, this.prevCellDims) : + this.cell(srcIdx, srcIdx, true); + this._addCell(srcIdx, srcIdx, newCell); let targets = srcObj.targetsParamView; for (let tgtObj of targets) { let tgtIdx = indexFor(this.nodes, tgtObj); if (tgtIdx != -1) { - let newCell = new N2MatrixCell(srcIdx, tgtIdx, srcObj, tgtObj, model, - this.cellDims, this.prevCellDims); - this._addCell(newCell, srcIdx, tgtIdx); + let newCell = makeNewGrid ? + new N2MatrixCell(srcIdx, tgtIdx, srcObj, tgtObj, model, + this.cellDims, this.prevCellDims) : + this.cell(srcIdx, tgtIdx, true); + this._addCell(srcIdx, tgtIdx, newCell); } } @@ -181,13 +181,20 @@ class N2Matrix { if (tgtObj.isUnknown()) { let tgtIdx = j; - let newCell = new N2MatrixCell(srcIdx, tgtIdx, srcObj, tgtObj, - model, this.cellDims, this.prevCellDims); - this._addCell(newCell, srcIdx, tgtIdx); + let newCell = makeNewGrid ? + new N2MatrixCell(srcIdx, tgtIdx, srcObj, tgtObj, + model, this.cellDims, this.prevCellDims) : + this.cell(srcIdx, tgtIdx, true); + this._addCell(srcIdx, tgtIdx, newCell); } } } } + + if (makeNewGrid) { + N2MatrixCell.updateBoundary(this.nodes[0].cell, + this.nodes[this.nodes.length - 1].cell); + } } /** @@ -271,7 +278,7 @@ class N2Matrix { let self = this; // For callbacks that change "this". Alternative to using .bind(). let selection = this.n2Groups.elements.selectAll('.n2cell') - .data(self.allCells, d => d.id); + .data(self.visibleCells, d => d.id); // Use D3 to join N2MatrixCells to SVG groups, and render shapes in them. let gEnter = selection.enter().append('g') @@ -279,9 +286,11 @@ class N2Matrix { .attr('transform', function (d) { if (self.lastClickWasLeft) { return 'translate(' + - (self.prevCellDims.size.width * (d.col - enterIndex) + + (self.prevCellDims.size.width * + (d.col - enterIndex) + self.prevCellDims.bottomRight.x) + ',' + - (self.prevCellDims.size.height * (d.row - enterIndex) + + (self.prevCellDims.size.height * + (d.row - enterIndex) + self.prevCellDims.bottomRight.y) + ')'; } @@ -309,9 +318,9 @@ class N2Matrix { gEnter.merge(selection) .transition(sharedTransition) .attr('transform', function (d) { - return 'translate(' + (self.cellDims.size.width * (d.col) + + return 'translate(' + (self.cellDims.size.width * d.col + self.cellDims.bottomRight.x) + ',' + - (self.cellDims.size.height * (d.row) + + (self.cellDims.size.height * d.row + self.cellDims.bottomRight.y) + ')'; }) // "this" refers to the element here, so leave it alone: @@ -356,7 +365,8 @@ class N2Matrix { .attr('transform', function (d) { if (self.lastClickWasLeft) return 'translate(0,' + (self.prevCellDims.size.height * (d.i - enterIndex)) + ')'; - let roc = (self.findRootOfChangeFunction) ? self.findRootOfChangeFunction(d.obj) : null; + let roc = (self.findRootOfChangeFunction) ? + self.findRootOfChangeFunction(d.obj) : null; if (roc) { let index0 = roc.prevRootIndex - self.layout.zoomedElement.prevRootIndex; return 'translate(0,' + (self.prevCellDims.size.height * index0) + ')'; @@ -377,7 +387,8 @@ class N2Matrix { .attr('transform', function (d) { if (self.lastClickWasLeft) return 'translate(0,' + (self.cellDims.size.height * (d.i - exitIndex)) + ')'; - let roc = (self.findRootOfChangeFunction) ? self.findRootOfChangeFunction(d.obj) : null; + let roc = (self.findRootOfChangeFunction) ? + self.findRootOfChangeFunction(d.obj) : null; if (roc) { let index = roc.rootIndex - self.layout.zoomedElement.rootIndex; return 'translate(0,' + (self.cellDims.size.height * index) + ')'; @@ -535,7 +546,7 @@ class N2Matrix { * @param {N2MatrixCell} cell The cell the event occured on. */ mouseOverOnDiagonal(cell) { - let leftTextWidthHovered = this.layout.visibleNodes[cell.row].nameWidthPx; + let leftTextWidthHovered = this.nodes[cell.row].nameWidthPx; // Loop over all elements in the matrix looking for other cells in the same column as let lineWidth = Math.min(5, this.nodeSize.width * .5, @@ -545,16 +556,22 @@ class N2Matrix { this.nodeSize.height * cell.row, leftTextWidthHovered, this.nodeSize.height, N2Style.color.highlightHovered); //highlight hovered + // TODO: Iterate over whole grid! for (let col = 0; col < this.layout.visibleNodes.length; ++col) { let leftTextWidthDependency = this.layout.visibleNodes[col].nameWidthPx; if (this.exists(cell.row, col)) { if (col != cell.row) { - new N2Arrow({ - 'end': { 'col': col, 'row': col }, - 'start': { 'col': cell.row, 'row': cell.row }, - 'color': N2Style.color.greenArrow, - 'width': lineWidth - }, this.n2Groups, this.nodeSize); + if (this.cell(cell.row, col).withinBounds()) { + new N2Arrow({ + 'end': { 'col': col, 'row': col }, + 'start': { 'col': cell.row, 'row': cell.row }, + 'color': N2Style.color.greenArrow, + 'width': lineWidth + }, this.n2Groups, this.nodeSize); + } + else { + console.log("Offscreen connector: ", cell.row, ',', col); + } //highlight var name this.hilight(-leftTextWidthDependency - this.layout.size.partitionTreeGap, @@ -566,13 +583,17 @@ class N2Matrix { // Now swap row and col if (this.exists(col, cell.row)) { if (col != cell.row) { - new N2Arrow({ - 'start': { 'col': col, 'row': col }, - 'end': { 'col': cell.row, 'row': cell.row }, - 'color': N2Style.color.redArrow, - 'width': lineWidth - }, this.n2Groups, this.nodeSize); - + if (this.cell(col, cell.row).withinBounds()) { + new N2Arrow({ + 'start': { 'col': col, 'row': col }, + 'end': { 'col': cell.row, 'row': cell.row }, + 'color': N2Style.color.redArrow, + 'width': lineWidth + }, this.n2Groups, this.nodeSize); + } + else { + console.log("Offscreen connector: ", col, ',', cell.row); + } //highlight var name this.hilight(-leftTextWidthDependency - this.layout.size.partitionTreeGap, this.nodeSize.height * col, leftTextWidthDependency, @@ -583,31 +604,36 @@ class N2Matrix { } drawArrowsParamView(startIndex, endIndex, nodeSize) { - let lineWidth = Math.min(5, nodeSize.width * .5, nodeSize.height * .5); + let lineWidth = Math.min(5, nodeSize.width * .5, nodeSize.height * .5); let boxStart = this.boxInfo[startIndex]; let boxEnd = this.boxInfo[endIndex]; - + // Draw multiple horizontal lines, but no more than one vertical line // for box-to-box connections let startIndices = [], endIndices = []; for (let startsI = boxStart.startI; startsI <= boxStart.stopI; ++startsI) { for (let endsI = boxEnd.startI; endsI <= boxEnd.stopI; ++endsI) { - if (this.cell(startsI, endsI) !== undefined) { + if (this.exists(startsI, endsI)) { startIndices.push(startsI); endIndices.push(endsI); } } } - + for (let i = 0; i < startIndices.length; ++i) { let startI = startIndices[i]; let endI = endIndices[i]; - new N2Arrow({ - 'start': { col: startI, row: startI }, - 'end': { col: endI, row: endI }, - 'color': (startIndex < endIndex) ? N2Style.color.greenArrow : N2Style.color.redArrow, - 'width': lineWidth - }, this.n2Groups, this.nodeSize); + if (this.cell(startI, endI).withinBounds()) { + new N2Arrow({ + 'start': { col: startI, row: startI }, + 'end': { col: endI, row: endI }, + 'color': (startIndex < endIndex) ? N2Style.color.greenArrow : N2Style.color.redArrow, + 'width': lineWidth + }, this.n2Groups, this.nodeSize); + } + else { + console.log("Offscreen connector: ", startI, ',', endI); + } } } @@ -622,12 +648,18 @@ class N2Matrix { let tgt = this.layout.visibleNodes[cell.col]; // let boxEnd = this.boxInfo[cell.col]; // not used? - new N2Arrow({ - 'start': { 'col': cell.row, 'row': cell.row }, - 'end': { 'col': cell.col, 'row': cell.col }, - 'color': N2Style.color.redArrow, - 'width': lineWidth - }, this.n2Groups, this.nodeSize); + if (cell.withinBounds()) { + new N2Arrow({ + 'start': { 'col': cell.row, 'row': cell.row }, + 'end': { 'col': cell.col, 'row': cell.col }, + 'color': N2Style.color.redArrow, + 'width': lineWidth + }, this.n2Groups, this.nodeSize); + } + else { + console.log("Offscreen connector: ", cell.row, ',', cell.col); + return; + } if (cell.row > cell.col) { let targetsWithCycleArrows = tgt.getNodesWithCycleArrows(); @@ -687,4 +719,24 @@ class N2Matrix { this.nodeSize.height * cell.col, leftTextWidthC, this.nodeSize.height, N2Style.color.greenArrow); } -} + + update(layout, lastClickWasLeft, findRootOfChangeFunction) { + this.layout = layout; + this.nodes = layout.visibleNodes; + + this.lastClickWasLeft = lastClickWasLeft; + this.findRootOfChangeFunction = findRootOfChangeFunction; + + Object.assign(this.prevNodeSize, this.nodeSize); + this.nodeSize = { + 'width': layout.size.diagram.width / this.nodes.length, + 'height': layout.size.diagram.height / this.nodes.length, + } + + N2CellRenderer.updateDims(this.nodeSize.width, this.nodeSize.height); + this.updateLevelOfDetailThreshold(layout.size.diagram.height); + + this._buildGrid(); + this._setupComponentBoxesAndGridLines(); + } +} \ No newline at end of file diff --git a/openmdao/visualization/n2_viewer/src/N2MatrixCell.js b/openmdao/visualization/n2_viewer/src/N2MatrixCell.js index 9b66ae0de8..0e1b59ee62 100644 --- a/openmdao/visualization/n2_viewer/src/N2MatrixCell.js +++ b/openmdao/visualization/n2_viewer/src/N2MatrixCell.js @@ -1,27 +1,62 @@ /** Base class for all cell renderers */ -class N2CellRendererBase { +class N2CellRenderer { /** * Set values shared by objects of all derived class types. - * @param {Object} dims Layout and dimensions for the current cell spec. - * @param {Object} prevDims Layout and dimensions for the previous cell spec. * @param {string} color The color to render all shapes in. * @param {string} className The CSS class to tag primary shapes with, used for selecting. */ - constructor(dims, prevDims, color, className) { - this.dims = dims; - this.prevDims = prevDims; + constructor(color, className) { this.color = color; this.className = className; } + static updateDims(baseWidth, baseHeight) { + if (!N2CellRenderer.dims) { + N2CellRenderer.prevDims = { + 'size': { 'width': 0, 'height': 0 }, + 'bottomRight': { 'x': 0, 'y': 0 }, + 'topLeft': { 'x': 0, 'y': 0 } + } + } + else { + for (let prop of ['size', 'bottomRight', 'topLeft']) { + Object.assign(N2CellRenderer.prevDims[prop], + N2CellRenderer.dims[prop]); + } + } + + N2CellRenderer.dims = { + 'size': { + 'width': baseWidth, + 'height': baseHeight + }, + 'bottomRight': { + 'x': baseWidth * .5, + 'y': baseHeight * .5 + }, + 'topLeft': { + 'x': baseWidth * -.5, + 'y': baseHeight * -.5 + } + } + } + + get dims() { + return N2CellRenderer.dims; + } + + get prevDims() { + return N2CellRenderer.prevDims; + } + /** Act like an abstract base class force derived classes to define. */ update() { - throw ("ERROR: N2CellRendererBase.update() called.") + throw ("ERROR: N2CellRenderer.update() called.") } /** Act like an abstract base class force derived classes to define. */ render() { - throw ("ERROR: N2CellRendererBase.render() called.") + throw ("ERROR: N2CellRenderer.render() called.") } /** Reposition an SVG element based on dimensions of the current cell size. */ @@ -46,7 +81,7 @@ class N2CellRendererBase { } /** Draws/updates an SVG circle for scalar types, with a transition animation. */ -class N2ScalarBase extends N2CellRendererBase { +class N2ScalarBase extends N2CellRenderer { /** * Invoke the superclass constructor with these values and "sMid" as a CSS class. @@ -54,8 +89,8 @@ class N2ScalarBase extends N2CellRendererBase { * @param {Object} prevDims Layout and dimensions for the previous cell spec. * @param {string} color The color to render all shapes in. */ - constructor(dims, prevDims, color) { - super(dims, prevDims, color, "sMid"); + constructor(color) { + super(color, "sMid"); } /** @@ -88,7 +123,7 @@ class N2ScalarBase extends N2CellRendererBase { } /** Draws/updates an SVG rect for vector types, with a transition animation. */ -class N2VectorBase extends N2CellRendererBase { +class N2VectorBase extends N2CellRenderer { /** * Invoke the superclass constructor with these values and "vMid" as a CSS class. @@ -96,8 +131,8 @@ class N2VectorBase extends N2CellRendererBase { * @param {Object} prevDims Layout and dimensions for the previous cell spec. * @param {string} color The color to render all shapes in. */ - constructor(dims, prevDims, color) { - super(dims, prevDims, color, "vMid"); + constructor(color) { + super(color, "vMid"); } /** @@ -134,15 +169,15 @@ class N2VectorBase extends N2CellRendererBase { } /** Draws/updates an SVG rect with a border for group types, with a transition animation. */ -class N2GroupBase extends N2CellRendererBase { +class N2GroupBase extends N2CellRenderer { /** * Invoke the superclass constructor with these values and "gMid" as a CSS class. * @param {Object} dims Layout and dimensions for the current cell spec. * @param {Object} prevDims Layout and dimensions for the previous cell spec. * @param {string} color The color to render all shapes in. */ - constructor(dims, prevDims, color) { - super(dims, prevDims, color, "gMid"); + constructor(color) { + super(color, "gMid"); } /** @@ -233,74 +268,74 @@ class N2GroupBase extends N2CellRendererBase { } class N2ScalarCell extends N2ScalarBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2VectorCell extends N2VectorBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2GroupCell extends N2GroupBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2ScalarScalarCell extends N2ScalarBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2ScalarVectorCell extends N2VectorBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2VectorScalarCell extends N2VectorBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2VectorVectorCell extends N2VectorBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2ScalarGroupCell extends N2GroupBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2GroupScalarCell extends N2GroupBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2VectorGroupCell extends N2GroupBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2GroupVectorCell extends N2GroupBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } class N2GroupGroupCell extends N2GroupBase { - constructor(dims, prevDims, color) { - super(dims, prevDims, color); + constructor(color) { + super(color); } } @@ -326,18 +361,40 @@ class N2MatrixCell { * @param {ModelData} model Reference to the model to get some info from it. * @param {Object} dims Layout and dimensions for the current cell spec. * @param {Object} prevDims Layout and dimensions for the previous cell spec. - * @param {N2CellRendererBase} renderer The object that draws the cell. + * @param {N2CellRenderer} renderer The object that draws the cell. */ - constructor(row, col, srcObj, tgtObj, model, dims, prevDims) { - this.row = row; - this.col = col; + constructor(row, col, srcObj, tgtObj, model) { + this.mainRow = row; + this.mainCol = col; this.srcObj = this.obj = srcObj; this.tgtObj = tgtObj; this.id = srcObj.id + "_" + tgtObj.id; this.symbolType = new SymbolType(this, model); - this.renderer = this._newRenderer(dims, prevDims); + this.renderer = this._newRenderer(); + this.tgtObj.cell = this; + } + + static updateBoundary(firstCell, lastCell) { + N2MatrixCell.boundary = { + 'first': { 'row': firstCell.mainRow, 'col': firstCell.mainCol }, + 'last': { 'row': lastCell.mainRow, 'col': lastCell.mainCol } + }; + + console.log("New top left: ", firstCell.mainRow, firstCell.mainCol); + console.log("New bottom right: ", lastCell.mainRow, lastCell.mainCol); + console.log(firstCell, lastCell) + } + + get row() { return this.mainRow - N2MatrixCell.boundary.first.row; } + get col() { return this.mainCol - N2MatrixCell.boundary.first.col; } + + withinBounds() { + return (this.mainRow >= N2MatrixCell.boundary.first.row && + this.mainCol >= N2MatrixCell.boundary.first.col && + this.mainRow <= N2MatrixCell.boundary.last.row && + this.mainCol <= N2MatrixCell.boundary.last.col); } /** @@ -377,20 +434,25 @@ class N2MatrixCell { * @param {Object} dims Layout and dimensions for the current cell spec. * @param {Object} prevDims Layout and dimensions for the previous cell spec. */ - _newRenderer(dims, prevDims) { + _newRenderer() { switch (this.symbolType.name) { - case 'scalar': return new N2ScalarCell(dims, prevDims, this.color()); - case 'vector': return new N2VectorCell(dims, prevDims, this.color()); - case 'group': return new N2GroupCell(dims, prevDims, this.color()); - case 'scalarScalar': return new N2ScalarScalarCell(dims, prevDims, this.color()); - case 'scalarVector': return new N2ScalarVectorCell(dims, prevDims, this.color()); - case 'vectorScalar': return new N2VectorScalarCell(dims, prevDims, this.color()); - case 'vectorVector': return new N2VectorVectorCell(dims, prevDims, this.color()); - case 'scalarGroup': return new N2ScalarGroupCell(dims, prevDims, this.color()); - case 'groupScalar': return new N2GroupScalarCell(dims, prevDims, this.color()); - case 'vectorGroup': return new N2VectorGroupCell(dims, prevDims, this.color()); - case 'groupVector': return new N2GroupVectorCell(dims, prevDims, this.color()); - case 'groupGroup': return new N2GroupGroupCell(dims, prevDims, this.color()); + case 'scalar': return new N2ScalarCell(this.color()); + case 'vector': return new N2VectorCell(this.color()); + case 'group': return new N2GroupCell(this.color()); + case 'scalarScalar': return new N2ScalarScalarCell(this.color()); + case 'scalarVector': return new N2ScalarVectorCell(this.color()); + case 'vectorScalar': return new N2VectorScalarCell(this.color()); + case 'vectorVector': return new N2VectorVectorCell(this.color()); + case 'scalarGroup': return new N2ScalarGroupCell(this.color()); + case 'groupScalar': return new N2GroupScalarCell(this.color()); + case 'vectorGroup': return new N2VectorGroupCell(this.color()); + case 'groupVector': return new N2GroupVectorCell(this.color()); + case 'groupGroup': return new N2GroupGroupCell(this.color()); } } -} \ No newline at end of file +} + +N2MatrixCell.boundary = { + 'first': { 'row': 0, 'col': 0 }, + 'last': { 'row': NaN, 'col': NaN } +}; \ No newline at end of file From d52c7b053f16391c82048a142cec43c7e0f8963c Mon Sep 17 00:00:00 2001 From: Tad Kollar Date: Mon, 11 Nov 2019 13:18:30 -0500 Subject: [PATCH 2/4] Added ability to detect offscreen connections. Added ability to toggle debugging messages in defaults.js. Changed matrix cell dimensions to a static object in N2CellRenderer. --- .../visualization/n2_viewer/src/ModelData.js | 130 ++++---- .../visualization/n2_viewer/src/N2Diagram.js | 7 +- .../visualization/n2_viewer/src/N2Layout.js | 40 ++- .../visualization/n2_viewer/src/N2Matrix.js | 315 +++++++++--------- .../n2_viewer/src/N2MatrixCell.js | 83 +++-- .../visualization/n2_viewer/src/N2TreeNode.js | 27 +- .../n2_viewer/src/N2UserInterface.js | 3 +- .../visualization/n2_viewer/src/defaults.js | 5 + openmdao/visualization/n2_viewer/src/utils.js | 12 + 9 files changed, 336 insertions(+), 286 deletions(-) diff --git a/openmdao/visualization/n2_viewer/src/ModelData.js b/openmdao/visualization/n2_viewer/src/ModelData.js index 9f42449fa6..449e2766ec 100644 --- a/openmdao/visualization/n2_viewer/src/ModelData.js +++ b/openmdao/visualization/n2_viewer/src/ModelData.js @@ -12,35 +12,36 @@ class ModelData { this.maxDepth = 1; this.idCounter = 0; this.unconnectedParams = 0; + this.nodePaths = {}; - console.time('ModelData._convertToN2TreeNodes'); + startTimer('ModelData._convertToN2TreeNodes'); this.root = this.tree = modelJSON.tree = this._convertToN2TreeNodes(modelJSON.tree); - console.timeEnd('ModelData._convertToN2TreeNodes'); + stopTimer('ModelData._convertToN2TreeNodes'); - console.time('ModelData._expandColonVars'); + startTimer('ModelData._expandColonVars'); this._expandColonVars(this.root); - console.timeEnd('ModelData._expandColonVars'); + stopTimer('ModelData._expandColonVars'); - console.time('ModelData._flattenColonGroups'); + startTimer('ModelData._flattenColonGroups'); this._flattenColonGroups(this.root); - console.timeEnd('ModelData._flattenColonGroups'); + stopTimer('ModelData._flattenColonGroups'); - console.time('ModelData._setParentsAndDepth'); + startTimer('ModelData._setParentsAndDepth'); this._setParentsAndDepth(this.root, null, 1); - console.timeEnd('ModelData._setParentsAndDepth'); + stopTimer('ModelData._setParentsAndDepth'); if (this.unconnectedParams > 0) console.info("Unconnected nodes: ", this.unconnectedParams); - console.time('ModelData._initSubSystemChildren'); + startTimer('ModelData._initSubSystemChildren'); this._initSubSystemChildren(this.root); - console.timeEnd('ModelData._initSubSystemChildren'); + stopTimer('ModelData._initSubSystemChildren'); - console.time('ModelData._computeConnections'); + startTimer('ModelData._computeConnections'); this._computeConnections(); - console.timeEnd('ModelData._computeConnections'); + stopTimer('ModelData._computeConnections'); - console.log("New model: ", modelJSON); + debugInfo("New model: ", this); // this.errorCheck(); } @@ -50,11 +51,11 @@ class ModelData { */ errorCheck(node = this.root) { if (!(node instanceof N2TreeNode)) - console.log('Node with problem: ', node); + debugInfo('Node with problem: ', node); for (let prop of ['parent', 'originalParent', 'parentComponent']) { if (node[prop] && !(node[prop] instanceof N2TreeNode)) - console.log('Node with problem ' + prop + ': ', node); + debugInfo('Node with problem ' + prop + ': ', node); } if (node.hasChildren()) { @@ -76,7 +77,8 @@ class ModelData { for (let i = 0; i < newNode.children.length; ++i) { newNode.children[i] = this._convertToN2TreeNodes(newNode.children[i]); newNode.children[i].parent = newNode; - if (exists(newNode.children[i].parentComponent)) newNode.children[i].parentComponent = newNode; + if (exists(newNode.children[i].parentComponent)) + newNode.children[i].parentComponent = newNode; } } @@ -197,6 +199,8 @@ class ModelData { node.absPathName += (node.parent.splitByColon) ? ":" : "."; } node.absPathName += node.name; + + this.nodePaths[node.absPathName] = node; } this.identifyUnconnectedParam(node); @@ -356,9 +360,7 @@ class ModelData { * @param {N2TreeNode[]} objArray Array to add to. */ _addLeaves(node, objArray) { - if (!node.isParam()) { - objArray.push(node); - } + if (!node.isParam()) { objArray.push(node); } if (node.hasChildren()) { for (let child of node.children) { @@ -368,89 +370,87 @@ class ModelData { } /** - * Iterate over the connections list, and find the two objects that - * make up each connection. + * Iterate over the connections list, and find the objects that make up + * each connection, and do some error checking. Store an array containing the + * target object and all of its parents in the source object and all of *its* + * parents. In the target object, store an array containing references to + * the begin and end of all the cycle arrows. */ _computeConnections() { let sysPathnames = this.sysPathnamesList; + let throwLbl = 'ModelData._computeConnections: '; for (let conn of this.conns) { // Process sources - let srcSplitArray = conn.src.split(/\.|:/); - let srcObj = this._getObjectInTree(this.root, srcSplitArray, 0); + let srcObj = this.nodePaths[conn.src]; + if (!srcObj) + throw (throwLbl + "Cannot find connection source " + conn.src); - if (srcObj == null) - throw ("Cannot find connection source " + conn.src); + let srcObjParents = [srcObj]; + if (!srcObj.isUnknown()) // source obj must be unknown + throw (throwLbl + "Found a source that is not an unknown."); - let srcObjArray = [srcObj]; - if (srcObj.type !== "unknown") // source obj must be unknown - throw ("There is a source that is not an unknown."); - - if (srcObj.hasChildren()) throw ("There is a source that has children."); + if (srcObj.hasChildren()) + throw (throwLbl + "Found a source that has children."); for (let obj = srcObj.parent; obj != null; obj = obj.parent) { - srcObjArray.push(obj); + srcObjParents.push(obj); } // Process targets - let tgtSplitArray = conn.tgt.split(/\.|:/); - let tgtObj = this._getObjectInTree(this.root, tgtSplitArray, 0); - - if (tgtObj == null) throw ("Cannot find connection target " + conn.tgt); + let tgtObj = this.nodePaths[conn.tgt]; + if (!tgtObj) + throw (throwLbl + "Cannot find connection target " + conn.tgt); - let tgtObjArrayParamView = [tgtObj]; - let tgtObjArrayHideParams = [tgtObj]; // Target obj must be a param - if (!tgtObj.isParam()) throw ("There is a target that is NOT a param."); - - if (tgtObj.hasChildren()) throw ("There is a target that has children."); + if (!tgtObj.isParam()) + throw (throwLbl + "Found a target that is NOT a param."); + if (tgtObj.hasChildren()) + throw (throwLbl + "Found a target that has children."); if (!tgtObj.parentComponent) - throw ("Target object " + conn.tgt + " has missing parentComponent."); + throw (throwLbl + "Target object " + conn.tgt + + " is missing a parentComponent."); - this._addLeaves(tgtObj.parentComponent, tgtObjArrayHideParams); //contaminate - for (let obj = tgtObj.parent; obj != null; obj = obj.parent) { - tgtObjArrayParamView.push(obj); - tgtObjArrayHideParams.push(obj); + let tgtObjParents = [tgtObj]; + for (let parentObj = tgtObj.parent; parentObj != null; parentObj = parentObj.parent) { + tgtObjParents.push(parentObj); } - for (let srcObj of srcObjArray) { - if (!srcObj.hasOwnProperty('targetsParamView')) - srcObj.targetsParamView = new Set(); - if (!srcObj.hasOwnProperty('targetsHideParams')) - srcObj.targetsHideParams = new Set(); - - tgtObjArrayParamView.forEach(item => srcObj.targetsParamView.add(item)); - tgtObjArrayHideParams.forEach(item => srcObj.targetsHideParams.add(item)); + for (let srcParent of srcObjParents) { + for (let tgtParent of tgtObjParents) { + srcParent.targetParentSet.add(tgtParent); + } } - let cycleArrowsArray = []; + /* + * The cycle_arrows object in each connection is an array of length-2 arrays, + * each of which is an index into the sysPathnames array. Using that array we + * can resolve the indexes to pathnames to the associated objects. + */ if (Array.isPopulatedArray(conn.cycle_arrows)) { + let cycleArrowsArray = []; let cycleArrows = conn.cycle_arrows; for (let cycleArrow of cycleArrows) { if (cycleArrow.length != 2) - throw ("cycleArrowsSplitArray length not 2, got " + + throw (throwLbl + "cycleArrowsSplitArray length not 2, got " + cycleArrow.length + ": " + cycleArrow); let srcPathname = sysPathnames[cycleArrow[0]]; let tgtPathname = sysPathnames[cycleArrow[1]]; - let splitArray = srcPathname.split(/\.|:/); - let arrowBeginObj = this._getObjectInTree(this.root, splitArray, 0); - if (arrowBeginObj == null) - throw ("Cannot find cycle arrows begin object " + srcPathname); + let arrowBeginObj = this.nodePaths[srcPathname]; + if (!arrowBeginObj) + throw (throwLbl + "Cannot find cycle arrows begin object " + srcPathname); - splitArray = tgtPathname.split(/\.|:/); - let arrowEndObj = this._getObjectInTree(this.root, splitArray, 0); - if (arrowEndObj == null) - throw ("Cannot find cycle arrows end object " + tgtPathname); + let arrowEndObj = this.nodePaths[tgtPathname]; + if (!arrowEndObj) + throw (throwLbl + "Cannot find cycle arrows end object " + tgtPathname); cycleArrowsArray.push({ "begin": arrowBeginObj, "end": arrowEndObj }); } - } - if (cycleArrowsArray.length > 0) { if (!tgtObj.parent.hasOwnProperty("cycleArrows")) { tgtObj.parent.cycleArrows = []; } diff --git a/openmdao/visualization/n2_viewer/src/N2Diagram.js b/openmdao/visualization/n2_viewer/src/N2Diagram.js index 7e0499a3bf..60f0aa240f 100644 --- a/openmdao/visualization/n2_viewer/src/N2Diagram.js +++ b/openmdao/visualization/n2_viewer/src/N2Diagram.js @@ -64,7 +64,7 @@ class N2Diagram { this.matrix = new N2Matrix(this.model, this.layout, this.dom.n2Groups, true, this.ui.findRootOfChangeFunction); - this.matrixMax = this.matrix; + // this.matrixMax = this.matrix; // TODO: Move to N2Layout this.scales = { @@ -553,13 +553,10 @@ class N2Diagram { this.layout = new N2Layout(this.model, this.zoomedElement, this.showLinearSolverNames, this.dims); this.ui.updateClickedIndices(); - /* + this.matrix = new N2Matrix(this.model, this.layout, this.dom.n2Groups, this.ui.lastClickWasLeft, this.ui.findRootOfChangeFunction, this.matrix.nodeSize); - */ - this.matrix.update(this.layout, this.ui.lastClickWasLeft, - this.ui.findRootOfChangeFunction); } this._updateScale(); diff --git a/openmdao/visualization/n2_viewer/src/N2Layout.js b/openmdao/visualization/n2_viewer/src/N2Layout.js index 0e650f99c4..a302270161 100644 --- a/openmdao/visualization/n2_viewer/src/N2Layout.js +++ b/openmdao/visualization/n2_viewer/src/N2Layout.js @@ -41,40 +41,40 @@ class N2Layout { this.svg = d3.select("#svgId"); this._setupTextRenderer(); - console.time('N2Layout._updateTextWidths'); + startTimer('N2Layout._updateTextWidths'); this._updateTextWidths(); - console.timeEnd('N2Layout._updateTextWidths'); + stopTimer('N2Layout._updateTextWidths'); - console.time('N2Layout._updateSolverTextWidths'); + startTimer('N2Layout._updateSolverTextWidths'); this._updateSolverTextWidths(); - console.timeEnd('N2Layout._updateSolverTextWidths'); + stopTimer('N2Layout._updateSolverTextWidths'); delete (this.textRenderer); - console.time('N2Layout._computeLeaves'); + startTimer('N2Layout._computeLeaves'); this._computeLeaves(); - console.timeEnd('N2Layout._computeLeaves'); + stopTimer('N2Layout._computeLeaves'); - console.time('N2Layout._computeColumnWidths'); + startTimer('N2Layout._computeColumnWidths'); this._computeColumnWidths(); - console.timeEnd('N2Layout._computeColumnWidths'); + stopTimer('N2Layout._computeColumnWidths'); - console.time('N2Layout._computeSolverColumnWidths'); + startTimer('N2Layout._computeSolverColumnWidths'); this._computeSolverColumnWidths(); - console.timeEnd('N2Layout._computeSolverColumnWidths'); + stopTimer('N2Layout._computeSolverColumnWidths'); - console.time('N2Layout._setColumnLocations'); + startTimer('N2Layout._setColumnLocations'); this._setColumnLocations(); - console.timeEnd('N2Layout._setColumnLocations'); + stopTimer('N2Layout._setColumnLocations'); - console.time('N2Layout._computeNormalizedPositions'); + startTimer('N2Layout._computeNormalizedPositions'); this._computeNormalizedPositions(this.model.root, 0, false, null); - console.timeEnd('N2Layout._computeNormalizedPositions'); + stopTimer('N2Layout._computeNormalizedPositions'); if (this.zoomedElement.parent) this.zoomedNodes.push(this.zoomedElement.parent); - console.time('N2Layout._computeSolverNormalizedPositions'); + startTimer('N2Layout._computeSolverNormalizedPositions'); this._computeSolverNormalizedPositions(this.model.root, 0, false, null); - console.timeEnd('N2Layout._computeSolverNormalizedPositions'); + stopTimer('N2Layout._computeSolverNormalizedPositions'); if (this.zoomedElement.parent) this.zoomedSolverNodes.push(this.zoomedElement.parent); @@ -92,7 +92,7 @@ class N2Layout { setTransitionPermission() { // Too many nodes, disable transitions. if (this.visibleNodes.length >= N2TransitionDefaults.maxNodes) { - console.log("Denying transitions: ", this.visibleNodes.length, + debugInfo("Denying transitions: ", this.visibleNodes.length, " visible nodes, max allowed: ", N2TransitionDefaults.maxNodes) // Return if already denied @@ -104,7 +104,7 @@ class N2Layout { d3.selection.prototype.delay = returnThis; } else { // OK, enable transitions. - console.log("Allowing transitions: ", this.visibleNodes.length, + debugInfo("Allowing transitions: ", this.visibleNodes.length, " visible nodes, max allowed: ", N2TransitionDefaults.maxNodes) // Return if already allowed @@ -344,8 +344,6 @@ class N2Layout { * merged as much as possible. */ - /** TODO: Document what the *0 variables are for */ - /** Recurse over the model tree and determine the coordinates and * size of visible nodes. If a parent is minimized, operations are * performed on it instead. @@ -363,7 +361,7 @@ class N2Layout { if (earliestMinimizedParent == null && isChildOfZoomed) { if (!node.varIsHidden) this.zoomedNodes.push(node); - if (!node.hasChildren() || node.isMinimized) { //at a "leaf" node + if (!node.hasChildren() || node.isMinimized) { // at a "leaf" node if (!node.varIsHidden) this.visibleNodes.push(node); earliestMinimizedParent = node; } diff --git a/openmdao/visualization/n2_viewer/src/N2Matrix.js b/openmdao/visualization/n2_viewer/src/N2Matrix.js index 3d9f7ff6fe..c45486abc6 100644 --- a/openmdao/visualization/n2_viewer/src/N2Matrix.js +++ b/openmdao/visualization/n2_viewer/src/N2Matrix.js @@ -1,3 +1,5 @@ + + /** * Use the model tree to build the matrix of parameters and connections, display, and * perform operations with it. @@ -30,27 +32,27 @@ class N2Matrix { prevNodeSize = { 'width': 0, 'height': 0 }) { this.layout = layout; - this.nodes = layout.visibleNodes; + this.diagNodes = layout.visibleNodes; this.n2Groups = n2Groups; this.lastClickWasLeft = lastClickWasLeft; this.findRootOfChangeFunction = findRootOfChangeFunction; - this.prevNodeSize = { 'width': 0, 'height': 0 }; + this.prevNodeSize = prevNodeSize; this.nodeSize = { - 'width': layout.size.diagram.width / this.nodes.length, - 'height': layout.size.diagram.height / this.nodes.length, + 'width': layout.size.diagram.width / this.diagNodes.length, + 'height': layout.size.diagram.height / this.diagNodes.length, } N2CellRenderer.updateDims(this.nodeSize.width, this.nodeSize.height); this.updateLevelOfDetailThreshold(layout.size.diagram.height); - console.time('N2Matrix._buildGrid'); + startTimer('N2Matrix._buildGrid'); this._buildGrid(model); - console.timeEnd('N2Matrix._buildGrid'); + stopTimer('N2Matrix._buildGrid'); - console.time('N2Matrix._setupComponentBoxesAndGridLines'); + startTimer('N2Matrix._setupComponentBoxesAndGridLines'); this._setupComponentBoxesAndGridLines(); - console.timeEnd('N2Matrix._setupComponentBoxesAndGridLines'); + stopTimer('N2Matrix._setupComponentBoxesAndGridLines'); } get cellDims() { return N2CellRenderer.dims; } @@ -63,11 +65,8 @@ class N2Matrix { * @returns False if the row doesn't exist or a column doesn't exist * in the row; true otherwise. */ - exists(row, col, useOffset = true) { - let offsetRow = useOffset? row + N2MatrixCell.boundary.first.row : row, - offsetCol = useOffset? col + N2MatrixCell.boundary.first.col : col; - - if (this.grid[offsetRow] && this.grid[offsetRow][offsetCol]) { return true; } + exists(row, col) { + if (this.grid[row] && this.grid[row][col]) { return true; } return false; } @@ -79,15 +78,12 @@ class N2Matrix { * @param {boolean} doThrow Whether to throw an exception if node undefined. * @returns {N2MatrixCell} The node if it exists, undefined otherwise. */ - cell(row, col, doThrow = false, useOffset = true) { - let offsetRow = useOffset? row + N2MatrixCell.boundary.first.row : row, - offsetCol = useOffset? col + N2MatrixCell.boundary.first.col : col; - - if (this.exists(row, col, useOffset)) { - return this.grid[offsetRow][offsetCol]; + cell(row, col, doThrow = false) { + if (this.exists(row, col)) { + return this.grid[row][col]; } else if (doThrow) { - throw "No node in matrix at (" + offsetRow + ", " + offsetCol + ")."; + throw "No node in matrix at (" + row + ", " + col + ")."; } return undefined; @@ -102,9 +98,9 @@ class N2Matrix { } tooMuchDetail() { - let tooMuch = (this.nodes.length >= this.levelOfDetailThreshold); + let tooMuch = (this.diagNodes.length >= this.levelOfDetailThreshold); - if (tooMuch) console.log("Too much detail.") + if (tooMuch) debugInfo("Too much detail.") return tooMuch; } @@ -124,77 +120,95 @@ class N2Matrix { return; } - if (!this.exists(row, col)) { this.grid[row][col] = newCell; } + this.grid[row][col] = newCell; this.visibleCells.push(newCell); } + /** + * For cells that are part of cycles, determine if there are parts + * of the cycle that are offscreen. If so, record them in the cell. + * @param {N2MatrixCell} cell The cell to check. + */ + _findUnseenCycleSources(cell) { + let node = cell.tgtObj; + let targetsWithCycleArrows = node.getNodesWithCycleArrows(); + + for (let twca of targetsWithCycleArrows) { + for (let ai of twca.cycleArrows) { + let found = false; + + // Check visible nodes on the diagonal. + for (let diagNode of this.diagNodes) { + let commonParent = diagNode.nearestCommonParent(ai.src); + if (diagNode.hasNode(ai.src, commonParent)) { + found = true; + break; + } + } + + if (!found) { + for (let tgt of ai.src.targetParentSet) { + if (tgt.absPathName == node.absPathName) { + cell.addOffScreenConn(ai.src, node) + } + } + } + } + } + } + /** * Set up N2MatrixCell arrays resembling a two-dimensional grid as the * matrix, but not an actual two dimensional array because most of * it would be unused. + * @param {ModelData} model Reference to the model, for creating cell objects. */ - _buildGrid(model = null) { + _buildGrid(model) { this.visibleCells = []; - let makeNewGrid = (this.grid == undefined); - - if (makeNewGrid) { - this.grid = {}; - } - else { - N2MatrixCell.updateBoundary(this.nodes[0].cell, - this.nodes[this.nodes.length - 1].cell); + this.grid = {}; - if (this.tooMuchDetail()) return; - } + if (this.tooMuchDetail()) return; - for (let srcIdx = 0; srcIdx < this.nodes.length; ++srcIdx) { - let srcObj = this.nodes[srcIdx]; + for (let srcIdx = 0; srcIdx < this.diagNodes.length; ++srcIdx) { + let srcObj = this.diagNodes[srcIdx]; // New row - if (!this.exists(srcIdx) && makeNewGrid) this.grid[srcIdx] = {}; + if (!this.grid.propExists(srcIdx)) this.grid[srcIdx] = {}; // On the diagonal - let newCell = makeNewGrid ? - new N2MatrixCell(srcIdx, srcIdx, srcObj, srcObj, model, - this.cellDims, this.prevCellDims) : - this.cell(srcIdx, srcIdx, true); - this._addCell(srcIdx, srcIdx, newCell); + let newDiagCell = new N2MatrixCell(srcIdx, srcIdx, srcObj, srcObj, model); + this._addCell(srcIdx, srcIdx, newDiagCell); + this._findUnseenCycleSources(newDiagCell); - let targets = srcObj.targetsParamView; + let targets = srcObj.targetParentSet; for (let tgtObj of targets) { - let tgtIdx = indexFor(this.nodes, tgtObj); + let tgtIdx = indexFor(this.diagNodes, tgtObj); if (tgtIdx != -1) { - let newCell = makeNewGrid ? - new N2MatrixCell(srcIdx, tgtIdx, srcObj, tgtObj, model, - this.cellDims, this.prevCellDims) : - this.cell(srcIdx, tgtIdx, true); + let newCell = new N2MatrixCell(srcIdx, tgtIdx, srcObj, tgtObj, model); this._addCell(srcIdx, tgtIdx, newCell); } + else { + if (tgtObj.isParamOrUnknown()) { + newDiagCell.addOffScreenConn(srcObj, tgtObj) + } + } } // Solver nodes if (srcObj.isParam()) { - for (let j = srcIdx + 1; j < this.nodes.length; ++j) { - let tgtObj = this.nodes[j]; + for (let j = srcIdx + 1; j < this.diagNodes.length; ++j) { + let tgtObj = this.diagNodes[j]; if (srcObj.parentComponent !== tgtObj.parentComponent) break; if (tgtObj.isUnknown()) { let tgtIdx = j; - let newCell = makeNewGrid ? - new N2MatrixCell(srcIdx, tgtIdx, srcObj, tgtObj, - model, this.cellDims, this.prevCellDims) : - this.cell(srcIdx, tgtIdx, true); + let newCell = new N2MatrixCell(srcIdx, tgtIdx, srcObj, tgtObj, model); this._addCell(srcIdx, tgtIdx, newCell); } } } } - - if (makeNewGrid) { - N2MatrixCell.updateBoundary(this.nodes[0].cell, - this.nodes[this.nodes.length - 1].cell); - } } /** @@ -208,9 +222,9 @@ class N2Matrix { // Find which component box each of the parameters belong in, // while finding the bounds of that box. Top and bottom // rows recorded for each node in this.boxInfo[]. - for (let ri = 1; ri < this.nodes.length; ++ri) { - let curNode = this.nodes[ri]; - let startINode = this.nodes[currentBox.startI]; + for (let ri = 1; ri < this.diagNodes.length; ++ri) { + let curNode = this.diagNodes[ri]; + let startINode = this.diagNodes[currentBox.startI]; if (startINode.parentComponent && curNode.parentComponent && startINode.parentComponent === curNode.parentComponent) { @@ -228,7 +242,7 @@ class N2Matrix { for (let i = 0; i < this.boxInfo.length; ++i) { let box = this.boxInfo[i]; if (box.startI == box.stopI) continue; - let curNode = this.nodes[box.startI]; + let curNode = this.diagNodes[box.startI]; if (!curNode.parentComponent) { throw "Parent component not found in box."; } @@ -240,8 +254,8 @@ class N2Matrix { //do this so you save old index for the exit() this.gridLines = []; if (!this.tooMuchDetail()) { - for (let i = 0; i < this.nodes.length; ++i) { - let obj = this.nodes[i]; + for (let i = 0; i < this.diagNodes.length; ++i) { + let obj = this.diagNodes[i]; let gl = { "i": i, "obj": obj }; this.gridLines.push(gl); } @@ -518,26 +532,54 @@ class N2Matrix { /** Add all the visible elements to the matrix. */ draw() { - console.time('N2Matrix.draw'); - console.log("maxDepth: ", this.layout.model.maxDepth, " zoomedElement depth: ", this.layout.zoomedElement.depth) + startTimer('N2Matrix.draw'); + // debugInfo("maxDepth: ", this.layout.model.maxDepth, " zoomedElement depth: ", this.layout.zoomedElement.depth) this._drawCells(); // Draw gridlines: if (!this.tooMuchDetail()) { - console.log("Drawing gridlines.") + debugInfo("Drawing gridlines.") this._drawHorizontalLines(); this._drawVerticalLines(); } else { - console.log("Erasing gridlines.") + debugInfo("Erasing gridlines.") this.n2Groups.gridlines.selectAll('.horiz_line').remove(); this.n2Groups.gridlines.selectAll(".vert_line").remove(); } this._drawComponentBoxes(); - console.timeEnd('N2Matrix.draw'); + stopTimer('N2Matrix.draw'); + + } + _drawOffscreenArrows(cell, lineWidth) { + if (!cell.offScreen.total) return; + + for (let offscreenNode of cell.offScreen.top.outgoing) { + debugInfo("Draw arrow on top going right away from " + + cell.tgtObj.absPathName + " to offscreen target " + + offscreenNode.absPathName); + } + + for (let offscreenNode of cell.offScreen.bottom.outgoing) { + debugInfo("Draw arrow on bottom going left from " + + cell.tgtObj.absPathName + " to offscreen target " + + offscreenNode.absPathName); + } + + for (let offscreenNode of cell.offScreen.top.incoming) { + debugInfo("Draw arrow on top coming down into " + + cell.tgtObj.absPathName + " from offscreen source " + + offscreenNode.absPathName); + } + + for (let offscreenNode of cell.offScreen.bottom.incoming) { + debugInfo("Draw arrow on bottom coming up into " + + cell.tgtObj.absPathName + " from offscreen source " + + offscreenNode.absPathName) + } } /** @@ -546,7 +588,7 @@ class N2Matrix { * @param {N2MatrixCell} cell The cell the event occured on. */ mouseOverOnDiagonal(cell) { - let leftTextWidthHovered = this.nodes[cell.row].nameWidthPx; + let leftTextWidthHovered = this.diagNodes[cell.row].nameWidthPx; // Loop over all elements in the matrix looking for other cells in the same column as let lineWidth = Math.min(5, this.nodeSize.width * .5, @@ -556,22 +598,19 @@ class N2Matrix { this.nodeSize.height * cell.row, leftTextWidthHovered, this.nodeSize.height, N2Style.color.highlightHovered); //highlight hovered - // TODO: Iterate over whole grid! + this._drawOffscreenArrows(cell, lineWidth); + for (let col = 0; col < this.layout.visibleNodes.length; ++col) { let leftTextWidthDependency = this.layout.visibleNodes[col].nameWidthPx; if (this.exists(cell.row, col)) { if (col != cell.row) { - if (this.cell(cell.row, col).withinBounds()) { - new N2Arrow({ - 'end': { 'col': col, 'row': col }, - 'start': { 'col': cell.row, 'row': cell.row }, - 'color': N2Style.color.greenArrow, - 'width': lineWidth - }, this.n2Groups, this.nodeSize); - } - else { - console.log("Offscreen connector: ", cell.row, ',', col); - } + + new N2Arrow({ + 'end': { 'col': col, 'row': col }, + 'start': { 'col': cell.row, 'row': cell.row }, + 'color': N2Style.color.greenArrow, + 'width': lineWidth + }, this.n2Groups, this.nodeSize); //highlight var name this.hilight(-leftTextWidthDependency - this.layout.size.partitionTreeGap, @@ -583,17 +622,13 @@ class N2Matrix { // Now swap row and col if (this.exists(col, cell.row)) { if (col != cell.row) { - if (this.cell(col, cell.row).withinBounds()) { - new N2Arrow({ - 'start': { 'col': col, 'row': col }, - 'end': { 'col': cell.row, 'row': cell.row }, - 'color': N2Style.color.redArrow, - 'width': lineWidth - }, this.n2Groups, this.nodeSize); - } - else { - console.log("Offscreen connector: ", col, ',', cell.row); - } + + new N2Arrow({ + 'start': { 'col': col, 'row': col }, + 'end': { 'col': cell.row, 'row': cell.row }, + 'color': N2Style.color.redArrow, + 'width': lineWidth + }, this.n2Groups, this.nodeSize); //highlight var name this.hilight(-leftTextWidthDependency - this.layout.size.partitionTreeGap, this.nodeSize.height * col, leftTextWidthDependency, @@ -610,30 +645,27 @@ class N2Matrix { // Draw multiple horizontal lines, but no more than one vertical line // for box-to-box connections - let startIndices = [], endIndices = []; + let arrows = []; for (let startsI = boxStart.startI; startsI <= boxStart.stopI; ++startsI) { for (let endsI = boxEnd.startI; endsI <= boxEnd.stopI; ++endsI) { if (this.exists(startsI, endsI)) { - startIndices.push(startsI); - endIndices.push(endsI); + arrows.push({ 'start': startsI, 'end': endsI }); } + /* + else { + throw ("Doesn't exist in matrix: " + startsI + ', ' + endsI); + } */ } } - for (let i = 0; i < startIndices.length; ++i) { - let startI = startIndices[i]; - let endI = endIndices[i]; - if (this.cell(startI, endI).withinBounds()) { - new N2Arrow({ - 'start': { col: startI, row: startI }, - 'end': { col: endI, row: endI }, - 'color': (startIndex < endIndex) ? N2Style.color.greenArrow : N2Style.color.redArrow, - 'width': lineWidth - }, this.n2Groups, this.nodeSize); - } - else { - console.log("Offscreen connector: ", startI, ',', endI); - } + for (let arrow of arrows) { + new N2Arrow({ + 'start': { 'col': arrow.start, 'row': arrow.start }, + 'end': { 'col': arrow.end, 'row': arrow.end }, + 'color': (startIndex < endIndex) ? + N2Style.color.greenArrow : N2Style.color.redArrow, + 'width': lineWidth + }, this.n2Groups, this.nodeSize); } } @@ -644,22 +676,15 @@ class N2Matrix { */ mouseOverOffDiagonal(cell) { let lineWidth = Math.min(5, this.nodeSize.width * .5, this.nodeSize.height * .5); - let src = this.layout.visibleNodes[cell.row]; - let tgt = this.layout.visibleNodes[cell.col]; - // let boxEnd = this.boxInfo[cell.col]; // not used? + let src = this.diagNodes[cell.row]; + let tgt = this.diagNodes[cell.col]; - if (cell.withinBounds()) { - new N2Arrow({ - 'start': { 'col': cell.row, 'row': cell.row }, - 'end': { 'col': cell.col, 'row': cell.col }, - 'color': N2Style.color.redArrow, - 'width': lineWidth - }, this.n2Groups, this.nodeSize); - } - else { - console.log("Offscreen connector: ", cell.row, ',', cell.col); - return; - } + new N2Arrow({ + 'start': { 'col': cell.row, 'row': cell.row }, + 'end': { 'col': cell.col, 'row': cell.col }, + 'color': N2Style.color.redArrow, + 'width': lineWidth + }, this.n2Groups, this.nodeSize); if (cell.row > cell.col) { let targetsWithCycleArrows = tgt.getNodesWithCycleArrows(); @@ -667,15 +692,13 @@ class N2Matrix { for (let twca of targetsWithCycleArrows) { for (let ai of twca.cycleArrows) { if (src.hasNode(ai.src)) { - for (let si of ai.arrows) { - let beginObj = si.begin; - let endObj = si.end; + for (let arrow of ai.arrows) { let firstBeginIndex = -1, firstEndIndex = -1; // find first begin index - for (let mi in this.layout.visibleNodes) { - let rtNode = this.layout.visibleNodes[mi]; - if (rtNode.hasNode(beginObj)) { + for (let mi in this.diagNodes) { + let rtNode = this.diagNodes[mi]; + if (rtNode.hasNode(arrow.begin)) { firstBeginIndex = mi; break; } @@ -685,9 +708,9 @@ class N2Matrix { } // find first end index - for (let mi in this.layout.visibleNodes) { - let rtNode = this.layout.visibleNodes[mi]; - if (rtNode.hasNode(endObj)) { + for (let mi in this.diagNodes) { + let rtNode = this.diagNodes[mi]; + if (rtNode.hasNode(arrow.end)) { firstEndIndex = mi; break; } @@ -719,24 +742,4 @@ class N2Matrix { this.nodeSize.height * cell.col, leftTextWidthC, this.nodeSize.height, N2Style.color.greenArrow); } - - update(layout, lastClickWasLeft, findRootOfChangeFunction) { - this.layout = layout; - this.nodes = layout.visibleNodes; - - this.lastClickWasLeft = lastClickWasLeft; - this.findRootOfChangeFunction = findRootOfChangeFunction; - - Object.assign(this.prevNodeSize, this.nodeSize); - this.nodeSize = { - 'width': layout.size.diagram.width / this.nodes.length, - 'height': layout.size.diagram.height / this.nodes.length, - } - - N2CellRenderer.updateDims(this.nodeSize.width, this.nodeSize.height); - this.updateLevelOfDetailThreshold(layout.size.diagram.height); - - this._buildGrid(); - this._setupComponentBoxesAndGridLines(); - } -} \ No newline at end of file +} diff --git a/openmdao/visualization/n2_viewer/src/N2MatrixCell.js b/openmdao/visualization/n2_viewer/src/N2MatrixCell.js index 0e1b59ee62..c57257f3e5 100644 --- a/openmdao/visualization/n2_viewer/src/N2MatrixCell.js +++ b/openmdao/visualization/n2_viewer/src/N2MatrixCell.js @@ -359,42 +359,23 @@ class N2MatrixCell { * @param {N2TreeNode} srcObj The node in the model tree this node is associated with. * @param {N2TreeNode} tgtObj The model tree node that this outputs to. * @param {ModelData} model Reference to the model to get some info from it. - * @param {Object} dims Layout and dimensions for the current cell spec. - * @param {Object} prevDims Layout and dimensions for the previous cell spec. * @param {N2CellRenderer} renderer The object that draws the cell. */ constructor(row, col, srcObj, tgtObj, model) { - this.mainRow = row; - this.mainCol = col; + this.row = row; + this.col = col; this.srcObj = this.obj = srcObj; this.tgtObj = tgtObj; this.id = srcObj.id + "_" + tgtObj.id; this.symbolType = new SymbolType(this, model); - this.renderer = this._newRenderer(); - this.tgtObj.cell = this; - } - - static updateBoundary(firstCell, lastCell) { - N2MatrixCell.boundary = { - 'first': { 'row': firstCell.mainRow, 'col': firstCell.mainCol }, - 'last': { 'row': lastCell.mainRow, 'col': lastCell.mainCol } - }; - - console.log("New top left: ", firstCell.mainRow, firstCell.mainCol); - console.log("New bottom right: ", lastCell.mainRow, lastCell.mainCol); - console.log(firstCell, lastCell) - } - - get row() { return this.mainRow - N2MatrixCell.boundary.first.row; } - get col() { return this.mainCol - N2MatrixCell.boundary.first.col; } - withinBounds() { - return (this.mainRow >= N2MatrixCell.boundary.first.row && - this.mainCol >= N2MatrixCell.boundary.first.col && - this.mainRow <= N2MatrixCell.boundary.last.row && - this.mainCol <= N2MatrixCell.boundary.last.col); + this.offScreen = { + 'top': { 'incoming': [], 'outgoing': []}, + 'bottom': { 'incoming': [], 'outgoing': []}, + 'total': 0 + } } /** @@ -430,6 +411,49 @@ class N2MatrixCell { return N2Style.color.connection; } + + /** + * An connection going "off-screen" was detected between two nodes. + * Determine whether the arrow should be in the top or bottom section of the + * matrix based on rootIndex, and add to the appropriate array of + * tracked offscreen connections. + * @param {N2TreeNode} srcNode Where the connection starts. + * @param {N2TreeNode} tgtNode Where the connection ends. + */ + addOffScreenConn(srcNode, tgtNode) { + let debugStr = ': ' + srcNode.absPathName + ' -> ' + tgtNode.absPathName; + + if (srcNode === this.tgtObj) { + // Outgoing + if (srcNode.rootIndex < tgtNode.rootIndex) { + // Top + debugInfo("New offscreen outgoing connection on top" + debugStr); + this.offScreen.top.outgoing.push(tgtNode); + } + else { + // Bottom + debugInfo("New offscreen outgoing connection on bottom" + debugStr); + this.offScreen.bottom.outgoing.push(tgtNode); + } + } + else { + // Incoming + if (srcNode.rootIndex < tgtNode.rootIndex) { + // Top + debugInfo("New offscreen incoming connection on top" + debugStr); + this.offScreen.top.incoming.push(srcNode); + } + else { + // Bottom + debugInfo("New offscreen incoming connection on bottom" + debugStr); + this.offScreen.bottom.incoming.push(srcNode); + } + } + + this.offScreen.total++; + // debugInfo("Total offscreen connections found: " + this.offScreen.total); + } + /** Choose a renderer based on our SymbolType. * @param {Object} dims Layout and dimensions for the current cell spec. * @param {Object} prevDims Layout and dimensions for the previous cell spec. @@ -450,9 +474,4 @@ class N2MatrixCell { case 'groupGroup': return new N2GroupGroupCell(this.color()); } } -} - -N2MatrixCell.boundary = { - 'first': { 'row': 0, 'col': 0 }, - 'last': { 'row': NaN, 'col': NaN } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/openmdao/visualization/n2_viewer/src/N2TreeNode.js b/openmdao/visualization/n2_viewer/src/N2TreeNode.js index 211e63f107..01486c6e6a 100644 --- a/openmdao/visualization/n2_viewer/src/N2TreeNode.js +++ b/openmdao/visualization/n2_viewer/src/N2TreeNode.js @@ -20,8 +20,7 @@ class N2TreeNode { Object.assign(this, origNode); // From old ClearConnections(): - this.targetsParamView = new Set(); - this.targetsHideParams = new Set(); + this.targetParentSet = new Set(); // Solver names may be empty, so set them to "None" instead. if (this.linear_solver == "") this.linear_solver = "None"; @@ -106,7 +105,7 @@ class N2TreeNode { } if (this.hasChildren()) { - for (let child of node.children) { + for (let child of this.children) { if (child._hasNodeInChildren(compareNode)) { return true; } @@ -119,11 +118,12 @@ class N2TreeNode { /** * Look for the supplied node in the lineage of this one. * @param {N2TreeNode} compareNode The node to look for. + * @param {N2TreeNode} [parentLimit = null] Stop searching at this common parent. * @returns {Boolean} True if the node is found, otherwise false. */ - hasNode(compareNode) { + hasNode(compareNode, parentLimit = null) { // Check parents first. - for (let obj = this; obj != null; obj = obj.parent) { + for (let obj = this; obj != null && obj !== parentLimit; obj = obj.parent) { if (obj === compareNode) { return true; } @@ -141,7 +141,7 @@ class N2TreeNode { if (this.cycleArrows) { arr.push(this); } if (this.hasChildren()) { - for (let child of node.children) { + for (let child of this.children) { child._getNodesInChildrenWithCycleArrows(arr); } } @@ -164,4 +164,19 @@ class N2TreeNode { return arr; } + + /** + * Find the closest parent shared by two nodes; farthest should be tree root. + * @param {N2TreeNode} other Another node to compare parents with. + * @returns {N2TreeNode} The first common parent found. + */ + nearestCommonParent(other) { + for (let myParent = this.parent; myParent != null; myParent = myParent.parent ) + for (let otherParent = other.parent; otherParent != null; otherParent = otherParent.parent) + if (myParent === otherParent) return myParent; + + // Should never get here because root is parent of all + debugInfo("No common parent found between two nodes: ", this, other); + return null; + } } \ No newline at end of file diff --git a/openmdao/visualization/n2_viewer/src/N2UserInterface.js b/openmdao/visualization/n2_viewer/src/N2UserInterface.js index 571d9fcd5d..cdbc99e0e6 100644 --- a/openmdao/visualization/n2_viewer/src/N2UserInterface.js +++ b/openmdao/visualization/n2_viewer/src/N2UserInterface.js @@ -70,7 +70,8 @@ class N2UserInterface { if (!node.hasChildren()) return; - if (node.depth > this.n2Diag.zoomedElement.depth) { // Don't allow minimizing of root node + // Don't allow minimizing of root node + if (node.depth > this.n2Diag.zoomedElement.depth) { this.rightClickedNode = node; this.findRootOfChangeFunction = this.findRootOfChangeForRightClick.bind(this); diff --git a/openmdao/visualization/n2_viewer/src/defaults.js b/openmdao/visualization/n2_viewer/src/defaults.js index fb0ffdb117..dbfaada090 100644 --- a/openmdao/visualization/n2_viewer/src/defaults.js +++ b/openmdao/visualization/n2_viewer/src/defaults.js @@ -37,4 +37,9 @@ let N2TransitionDefaults = { 'maxNodes': 150 } +let DebugFlags = { + 'timings': false, + 'info': false +} + // Object.freeze(N2TransitionDefaults); \ No newline at end of file diff --git a/openmdao/visualization/n2_viewer/src/utils.js b/openmdao/visualization/n2_viewer/src/utils.js index 2132be6805..e9929fc923 100644 --- a/openmdao/visualization/n2_viewer/src/utils.js +++ b/openmdao/visualization/n2_viewer/src/utils.js @@ -75,3 +75,15 @@ d3.selection.prototype.originalFuncs = { d3.selection.prototype.transitionAllowed = true; function returnThis() { return this; } + +function startTimer(label) { + if (DebugFlags.timings) console.time(label); +} + +function stopTimer(label) { + if (DebugFlags.timings) console.timeEnd(label); +} + +function debugInfo(msg) { + if (DebugFlags.info) console.log(msg); +} \ No newline at end of file From 47d85a25a6fa7970dc00632a81b2dbce665f8778 Mon Sep 17 00:00:00 2001 From: Tad Kollar Date: Tue, 12 Nov 2019 09:30:27 -0500 Subject: [PATCH 3/4] Updated a few inline docs. --- openmdao/visualization/n2_viewer/src/N2Matrix.js | 8 ++++---- openmdao/visualization/n2_viewer/src/N2MatrixCell.js | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openmdao/visualization/n2_viewer/src/N2Matrix.js b/openmdao/visualization/n2_viewer/src/N2Matrix.js index c45486abc6..709a24fe19 100644 --- a/openmdao/visualization/n2_viewer/src/N2Matrix.js +++ b/openmdao/visualization/n2_viewer/src/N2Matrix.js @@ -1,5 +1,3 @@ - - /** * Use the model tree to build the matrix of parameters and connections, display, and * perform operations with it. @@ -11,8 +9,6 @@ * @property {number} levelOfDetailThreshold Don't draw elements below this size in pixels. * @property {Object} nodeSize Width and height of each node in the matrix. * @property {Object} prevNodeSize Width and height of each node in the previous matrix. - * @property {Object} cellDims nodeSize with computed coordinates. - * @property {Object} prevCellDims prevNodeSize with computed coordinates. * @property {Object[][]} grid Object keys corresponding to rows and columns. * @property {Array} visibleCells One-dimensional array of all cells, for D3 processing. * @property {Array} boxInfo Component box dimensions. @@ -97,6 +93,10 @@ class N2Matrix { this.levelOfDetailThreshold = height / 3; } + /** + * Compare the number of visible nodes to the amount allowed by + * the threshold setting. + */ tooMuchDetail() { let tooMuch = (this.diagNodes.length >= this.levelOfDetailThreshold); diff --git a/openmdao/visualization/n2_viewer/src/N2MatrixCell.js b/openmdao/visualization/n2_viewer/src/N2MatrixCell.js index c57257f3e5..f8706a5b40 100644 --- a/openmdao/visualization/n2_viewer/src/N2MatrixCell.js +++ b/openmdao/visualization/n2_viewer/src/N2MatrixCell.js @@ -41,10 +41,12 @@ class N2CellRenderer { } } + /** Enable access to the static dims variable through 'this'. */ get dims() { return N2CellRenderer.dims; } + /** Enable access to the static prevDims variable through 'this'. */ get prevDims() { return N2CellRenderer.prevDims; } From 0e63aa64310a413fb22d4a46c595974017c9d2ad Mon Sep 17 00:00:00 2001 From: Tad Kollar Date: Tue, 12 Nov 2019 16:05:54 -0500 Subject: [PATCH 4/4] Added a small comment --- openmdao/visualization/n2_viewer/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openmdao/visualization/n2_viewer/index.html b/openmdao/visualization/n2_viewer/index.html index 8b527094a6..daa67fe12e 100644 --- a/openmdao/visualization/n2_viewer/index.html +++ b/openmdao/visualization/n2_viewer/index.html @@ -15,6 +15,7 @@
+ @@ -65,4 +66,4 @@ {n2diagram_lib} {n2userinterface_lib} {defaults_lib} -{ptn2_lib} \ No newline at end of file +{ptn2_lib}