/*
* Copyright (C) 1998-2024 by Northwoods Software Corporation. All Rights Reserved.
*/
/*
* This is an extension and not part of the main GoJS library.
* Note that the API for this class may change with any version, even point releases.
* If you intend to use an extension in production, you should copy the code to your own source directory.
* Extensions can be found in the GoJS kit under the extensions or extensionsJSM folders.
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
*/
import * as go from 'gojs';
/**
* This {@link go.Layout} positions non-Link Parts into a table according to the values of
* {@link go.GraphObject.row}, {@link go.GraphObject.column}, {@link go.GraphObject.rowSpan}, {@link go.GraphObject.columnSpan},
* {@link go.GraphObject.alignment}, {@link go.GraphObject.stretch}.
* If the value of GraphObject.stretch is not {@link go.Stretch.None}, the Part will be sized
* according to the available space in the cell(s).
*
* You can specify constraints for whole rows or columns by calling
* {@link getRowDefinition} or {@link getColumnDefinition} and setting one of the following properties:
* {@link go.RowColumnDefinition.alignment}, {@link go.RowColumnDefinition.height}, {@link go.RowColumnDefinition.width},
* {@link go.RowColumnDefinition.maximum}, {@link go.RowColumnDefinition.minimum}, {@link go.RowColumnDefinition.stretch}.
*
* The {@link defaultAlignment} and {@link defaultStretch} properties apply to all parts if not specified
* on the individual Part or in the corresponding row or column definition.
*
* At the current time, there is no support for separator lines
* ({@link go.RowColumnDefinition.separatorStroke}, {@link go.RowColumnDefinition.separatorStrokeWidth},
* and {@link go.RowColumnDefinition.separatorDashArray} properties)
* nor background ({@link go.RowColumnDefinition.background} and {@link go.RowColumnDefinition.coversSeparators} properties).
* There is no support for {@link go.RowColumnDefinition.sizing}, either.
*
* If you want to experiment with this extension, try the Table Layout sample.
* @category Layout Extension
*/
export class TableLayout extends go.Layout {
private _defaultAlignment: go.Spot;
private _defaultStretch: go.Stretch;
private _rowDefs: any;
private _colDefs: any;
constructor(init?: Partial) {
super();
this._defaultAlignment = go.Spot.Default;
this._defaultStretch = go.Stretch.Default;
this._rowDefs = [];
this._colDefs = [];
if (init) Object.assign(this, init);
}
/**
* Gets or sets the alignment to use by default for Parts in rows (vertically) and in columns (horizontally).
*
* The default value is {@link go.Spot.Default}.
* Setting this property does not raise any events.
*/
get defaultAlignment(): go.Spot {
return this._defaultAlignment;
}
set defaultAlignment(val: go.Spot) {
if (!this._defaultAlignment.equals(val)) {
if (!(val instanceof go.Spot)) throw new Error("new defaultAlignment value is not a Spot");
this._defaultAlignment = val;
this.invalidateLayout();
}
}
/**
* Gets or sets whether Parts should be stretched in rows (vertically) and in columns (horizontally).
*
* The default value is {@link go.Stretch.Default}.
* Setting this property does not raise any events.
*/
get defaultStretch(): go.Stretch {
return this._defaultStretch;
}
set defaultStretch(val: go.Stretch) {
if (this._defaultStretch !== val) {
this._defaultStretch = val;
this.invalidateLayout();
}
}
/**
* This read-only property returns the number of rows in this TableLayout.
* This value is only valid after the layout has been performed.
*/
get rowCount(): number {
return this._rowDefs.length;
}
/**
* This read-only property returns the number of columns in this TableLayout.
* This value is only valid after the layout has been performed.
*/
get columnCount(): number {
return this._colDefs.length;
}
/**
* Copies properties to a cloned Layout.
*/
override cloneProtected(copy: this): void {
super.cloneProtected(copy);
copy._defaultAlignment = this._defaultAlignment;
copy._defaultStretch = this._defaultStretch;
for (let i = 0; i < this._rowDefs.length; i++) {
const def = this._rowDefs[i];
copy._rowDefs.push(def !== undefined ? def.copy() : def);
}
for (let i = 0; i < this._colDefs.length; i++) {
const def = this._colDefs[i];
copy._colDefs.push(def !== undefined ? def.copy() : def);
}
}
/**
* Interpret {@link go.Part.margin} differently than normal.
*/
override getLayoutBounds(part: go.Part, rect?: go.Rect): go.Rect {
const func = this.boundsComputation;
if (func !== null) {
if (!rect) rect = new go.Rect();
return func(part, this, rect);
}
if (!rect) return part.actualBounds;
rect.set(part.actualBounds);
return rect;
}
/**
* Gets the {@link go.RowColumnDefinition} for a particular row in this TableLayout.
* If you ask for the definition of a row at or beyond the {@link rowCount},
* it will automatically create one and return it.
* @param idx - the non-negative zero-based integer row index.
*/
getRowDefinition(idx: number): go.RowColumnDefinition {
if (idx < 0) throw new Error('Row index must be non-negative, not: ' + idx);
idx = Math.round(idx);
const defs = this._rowDefs;
let d = defs[idx];
if (d === undefined) {
d = new go.RowColumnDefinition();
// .panel remains null
d.isRow = true;
d.index = idx;
defs[idx] = d;
}
return d;
}
/**
* Returns the row at a given y-coordinate in document coordinates.
* This information is only valid when this layout has been performed and {@link go.Layout.isValidLayout} is true.
*
* If the point is above row 0, this method returns -1.
* If the point below the last row, this returns the last row + 1.
* @param y
* @returns a zero-based integer
* @see {@link findColumnForDocumentX}
*/
findRowForDocumentY(y: number): number {
y -= this.arrangementOrigin.y;
if (y < 0) return -1;
let total = 0.0;
const it = this._rowDefs;
const l = it.length;
for (let i = 0; i < l; i++) {
const def = it[i];
if (def === undefined) continue;
total += def.total;
if (y < total) {
return i;
}
}
return l;
}
/**
* Gets the {@link go.RowColumnDefinition} for a particular column in this TableLayout.
* If you ask for the definition of a column at or beyond the {@link columnCount},
* it will automatically create one and return it.
* @param idx - the non-negative zero-based integer column index.
*/
getColumnDefinition(idx: number): go.RowColumnDefinition {
if (idx < 0) throw new Error('Column index must be non-negative, not: ' + idx);
idx = Math.round(idx);
const defs = this._colDefs;
let d = defs[idx];
if (d === undefined) {
d = new go.RowColumnDefinition();
// .panel remains null
d.isRow = false;
d.index = idx;
defs[idx] = d;
}
return d;
}
/**
* Returns the cell at a given x-coordinate in document coordinates.
* This information is only valid when this layout has been performed and {@link go.Layout.isValidLayout} is true.
*
* If the point is to left of the column 0, this method returns -1.
* If the point to to the right of the last column, this returns the last column + 1.
* @param x
* @returns a zero-based integer
* @see {@link findRowForDocumentY}
*/
findColumnForDocumentX(x: number): number {
x -= this.arrangementOrigin.x;
if (x < 0) return -1;
let total = 0.0;
const it = this._colDefs;
const l = it.length;
for (let i = 0; i < l; i++) {
const def = it[i];
if (def === undefined) continue;
total += def.total;
if (x < total) {
return i;
}
}
return l;
}
/**
* @hidden @internal
* Only ever called from TableLayout's measure and arrange
*/
private getEffectiveTableStretch(
child: go.Part,
row: go.RowColumnDefinition,
col: go.RowColumnDefinition
): go.Stretch {
const effectivestretch = child.stretch;
if (effectivestretch !== go.Stretch.Default) return effectivestretch;
// which directions are we stretching?
// undefined = default
let horizontal;
let vertical;
switch (row.stretch) {
case go.Stretch.Default:
case go.Stretch.Horizontal:
break;
case go.Stretch.Vertical:
vertical = true;
break;
case go.Stretch.Fill:
vertical = true;
break;
}
switch (col.stretch) {
case go.Stretch.Default:
case go.Stretch.Vertical:
break;
case go.Stretch.Horizontal:
horizontal = true;
break;
case go.Stretch.Fill:
horizontal = true;
break;
}
const str = this.defaultStretch;
if (horizontal === undefined && (str === go.Stretch.Horizontal || str === go.Stretch.Fill)) {
horizontal = true;
} else {
horizontal = false;
}
if (vertical === undefined && (str === go.Stretch.Vertical || str === go.Stretch.Fill)) {
vertical = true;
} else {
vertical = false;
}
if (horizontal === true && vertical === true) return go.Stretch.Fill;
if (horizontal === true) return go.Stretch.Horizontal;
if (vertical === true) return go.Stretch.Vertical;
return go.Stretch.None; // Everything else is none by default
}
/**
* This method performs the measuring and arranging of the table, assiging positions to each part.
* @param coll - A collection of {@link go.Part}s.
*/
override doLayout(coll: go.Iterable): void {
this.arrangementOrigin = this.initialOrigin(this.arrangementOrigin);
// put all eligible Parts that are not Links into an Array
const parts = new go.List();
this.collectParts(coll).each((p) => {
if (!(p instanceof go.Link)) {
parts.add(p);
}
});
if (this.diagram !== null) {
this.diagram.startTransaction('TableLayout');
const union = new go.Size();
// this calls .beforeMeasure(parts, rowcol)
const rowcol = this.measureTable(Infinity, Infinity, parts, union, 0, 0);
this.arrangeTable(parts, union, rowcol);
this.afterArrange(parts, rowcol);
this.diagram.commitTransaction('TableLayout');
}
}
/**
* Override this method in order to perform some operations before measuring.
* By default this method does nothing.
* @virtual
*/
protected beforeMeasure(parts: go.List, rowcol: Array>>): void {}
/**
* Override this method in order to perform some operations after arranging.
* By default this method does nothing.
* @virtual
*/
protected afterArrange(parts: go.List, rowcol: Array>>): void {}
/**
* @hidden @internal
*/
measureTable(
width: number,
height: number,
children: go.List,
union: go.Size,
minw: number,
minh: number
): Array> {
let l = children.length;
// Make the array that holds [rows][cols] of the table
const rowcol: Array> = []; // saved (so no temp array) starts as an array of rows, will end up [row][col][cell]
for (let i = 0; i < l; i++) {
const child = children.elt(i);
if (!rowcol[child.row]) {
rowcol[child.row] = []; // make new column for this row
}
if (!rowcol[child.row][child.column]) {
rowcol[child.row][child.column] = []; // new list for this cell
}
rowcol[child.row][child.column].push(child); // push child into right cell
}
this.beforeMeasure(children, rowcol);
// Reset the row/col definitions because the ones from last measure are irrelevant
const resetCols = []; // keep track of which columns we've already reset
// Objects that span multiple columns and
const spanners = [];
const nosize = [];
// These hashes are used to tally the number of rows and columns that do not have a size
const nosizeCols = { count: 0 };
const nosizeRows = { count: 0 };
let colleft = width;
let rowleft = height;
let defs = this._rowDefs;
l = defs.length;
for (let i = 0; i < l; i++) {
const def = defs[i];
if (def !== undefined) def.actual = 0;
}
defs = this._colDefs;
l = defs.length;
for (let i = 0; i < l; i++) {
const def = defs[i];
if (def !== undefined) def.actual = 0;
}
let lrow = rowcol.length; // number of rows
let lcol = 0;
for (let i = 0; i < lrow; i++) {
if (!rowcol[i]) continue;
lcol = Math.max(lcol, rowcol[i].length); // column length in this row
}
// Go through each cell (first pass)
let amt = 0.0;
lrow = rowcol.length; // number of rows
for (let i = 0; i < lrow; i++) {
if (!rowcol[i]) continue;
lcol = rowcol[i].length; // column length in this row
const rowHerald = this.getRowDefinition(i);
rowHerald.measured = 0; // Reset rows (only on first pass)
for (let j = 0; j < lcol; j++) {
// foreach column j in row i...
if (!rowcol[i][j]) continue;
const colHerald = this.getColumnDefinition(j);
if (resetCols[j] === undefined) {
// make sure we only reset these once
colHerald.measured = 0;
resetCols[j] = true;
}
const cell = rowcol[i][j];
const len = cell.length;
for (let k = 0; k < len; k++) {
// foreach element in cell, measure
const child: go.Part = cell[k];
// Skip children that span more than one row or column or do not have a set size
const spanner = child.rowSpan > 1 || child.columnSpan > 1;
if (spanner) {
spanners.push(child);
// We used to not measure spanners twice, but now we do
// The reason is that there may be a row whose size
// is dictated by an object with columnSpan 2+ and vice versa
// continue;
}
const marg = child.margin as go.Margin;
const margw = marg.right + marg.left;
const margh = marg.top + marg.bottom;
const stretch = this.getEffectiveTableStretch(child, rowHerald, colHerald);
const dsize = child.resizeObject.desiredSize;
const realwidth = !isNaN(dsize.width);
const realheight = !isNaN(dsize.height);
const realsize = realwidth && realheight;
if (!spanner && stretch !== go.Stretch.None && !realsize) {
if (
(nosizeCols as any)[j] === undefined &&
(stretch === go.Stretch.Fill || stretch === go.Stretch.Horizontal)
) {
(nosizeCols as any)[j] = -1;
nosizeCols.count++;
}
if (
(nosizeRows as any)[i] === undefined &&
(stretch === go.Stretch.Fill || stretch === go.Stretch.Vertical)
) {
(nosizeRows as any)[i] = -1;
nosizeRows.count++;
}
nosize.push(child);
}
if (stretch !== go.Stretch.None) {
const unrestrictedSize = new go.Size(NaN, NaN);
// if (stretch !== go.Stretch.Horizontal) unrestrictedSize.height = rowHerald.minimum;
// if (stretch !== go.Stretch.Vertical) unrestrictedSize.width = colHerald.minimum;
// ??? allow resizing during measure phase
child.resizeObject.desiredSize = unrestrictedSize;
child.ensureBounds();
}
const m = this.getLayoutBounds(child);
const mwidth = Math.max(m.width + margw, 0);
const mheight = Math.max(m.height + margh, 0);
// Make sure the heralds have the right layout size
// the row/column should use the largest measured size of any
// GraphObject contained, constrained by mins and maxes
if (
child.rowSpan === 1 &&
(realheight || stretch === go.Stretch.None || stretch === go.Stretch.Horizontal)
) {
const def = this.getRowDefinition(i);
amt = Math.max(mheight - def.actual, 0);
if (amt > rowleft) amt = rowleft;
def.measured = def.measured + amt;
def.actual = def.actual + amt;
rowleft = Math.max(rowleft - amt, 0);
}
if (
child.columnSpan === 1 &&
(realwidth || stretch === go.Stretch.None || stretch === go.Stretch.Vertical)
) {
const def = this.getColumnDefinition(j);
amt = Math.max(mwidth - def.actual, 0);
if (amt > colleft) amt = colleft;
def.measured = def.measured + amt;
def.actual = def.actual + amt;
colleft = Math.max(colleft - amt, 0);
}
} // end cell
} // end col
} // end row
// For objects of no desired size we allocate what is left as we go,
// or else what is already in the column
let totalColWidth = 0.0;
let totalRowHeight = 0.0;
l = this.columnCount;
for (let i = 0; i < l; i++) {
if (this._colDefs[i] === undefined) continue;
totalColWidth += this.getColumnDefinition(i).measured;
}
l = this.rowCount;
for (let i = 0; i < l; i++) {
if (this._rowDefs[i] === undefined) continue;
totalRowHeight += this.getRowDefinition(i).measured;
}
colleft = Math.max(width - totalColWidth, 0);
rowleft = Math.max(height - totalRowHeight, 0);
const originalrowleft = rowleft;
const originalcolleft = colleft;
// Determine column sizes for the yet-to-be-sized columns
l = nosize.length;
for (let i = 0; i < l; i++) {
const child = nosize[i];
const rowHerald = this.getRowDefinition(child.row);
const colHerald = this.getColumnDefinition(child.column);
// We want to gather the largest difference between desired and expected col/row sizes
const mb = this.getLayoutBounds(child);
const marg = child.margin as go.Margin;
const margw = marg.right + marg.left;
const margh = marg.top + marg.bottom;
if (colHerald.measured === 0 && (nosizeCols as any)[child.column] !== undefined) {
(nosizeCols as any)[child.column] = Math.max(
mb.width + margw,
(nosizeCols as any)[child.column]
);
} else {
(nosizeCols as any)[child.column] = null; // obey the column herald
}
if (rowHerald.measured === 0 && (nosizeRows as any)[child.row] !== undefined) {
(nosizeRows as any)[child.row] = Math.max(
mb.height + margh,
(nosizeRows as any)[child.row]
);
} else {
(nosizeRows as any)[child.row] = null; // obey the row herald
}
}
// we now have the size that all these columns prefer to be
// we also have the amount left over
let desiredRowTotal = 0.0;
let desiredColTotal = 0.0;
for (const i in nosizeRows) {
if (i !== 'count') desiredRowTotal += (nosizeRows as any)[i];
}
for (const i in nosizeCols) {
if (i !== 'count') desiredColTotal += (nosizeCols as any)[i];
}
const allowedSize = new go.Size(); // used in stretch and span loops
// Deal with objects that have a stretch
for (let i = 0; i < l; i++) {
const child = nosize[i];
const rowHerald = this.getRowDefinition(child.row);
const colHerald = this.getColumnDefinition(child.column);
let w = 0.0;
if (isFinite(colHerald.width)) {
w = colHerald.width;
} else {
if (isFinite(colleft) && (nosizeCols as any)[child.column] !== null) {
if (desiredColTotal === 0) w = colHerald.actual + colleft;
else {
w =
/*colHerald.actual +*/ ((nosizeCols as any)[child.column] / desiredColTotal) *
originalcolleft;
}
} else {
// Only use colHerald.actual if it was nonzero before this loop
if ((nosizeCols as any)[child.column] !== null) w = colleft;
else w = colHerald.actual || colleft;
// w = nosizeCols[child.column] || colleft; // Older, less correct way
}
w = Math.max(0, w - colHerald.computeEffectiveSpacing());
}
let h = 0.0;
if (isFinite(rowHerald.height)) {
h = rowHerald.height;
} else {
if (isFinite(rowleft) && (nosizeRows as any)[child.row] !== null) {
if (desiredRowTotal === 0) h = rowHerald.actual + rowleft;
else {
h =
/*rowHerald.actual +*/ ((nosizeRows as any)[child.row] / desiredRowTotal) *
originalrowleft;
}
} else {
// Only use rowHerald.actual if it was nonzero before this loop
if ((nosizeRows as any)[child.row] !== null) h = rowleft;
else h = rowHerald.actual || rowleft;
// h = nosizeRows[child.row] || rowleft; // Older, less correct way
}
h = Math.max(0, h - rowHerald.computeEffectiveSpacing());
}
allowedSize.setTo(
Math.max(colHerald.minimum, Math.min(w, colHerald.maximum)),
Math.max(rowHerald.minimum, Math.min(h, rowHerald.maximum))
);
// Which way do we care about fill:
const stretch = this.getEffectiveTableStretch(child, rowHerald, colHerald);
// This used to set allowedSize height/width to Infinity,
// but we can only set it to the current row/column space, plus rowleft/colleft values, at most.
switch (stretch) {
case go.Stretch.Horizontal: // H stretch means it can be as large as its wants vertically
allowedSize.height = Math.max(allowedSize.height, rowHerald.actual + rowleft);
break;
case go.Stretch.Vertical: // vice versa
allowedSize.width = Math.max(allowedSize.width, colHerald.actual + colleft);
break;
}
const marg = child.margin as go.Margin;
const margw = marg.right + marg.left;
const margh = marg.top + marg.bottom;
const m = this.getLayoutBounds(child);
let mwidth = Math.max(m.width + margw, 0);
let mheight = Math.max(m.height + margh, 0);
if (isFinite(colleft)) mwidth = Math.min(mwidth, allowedSize.width);
if (isFinite(rowleft)) mheight = Math.min(mheight, allowedSize.height);
let oldAmount = 0.0;
oldAmount = rowHerald.actual;
rowHerald.actual = Math.max(rowHerald.actual, mheight);
rowHerald.measured = Math.max(rowHerald.measured, mheight);
amt = rowHerald.actual - oldAmount;
rowleft = Math.max(rowleft - amt, 0);
oldAmount = colHerald.actual;
colHerald.actual = Math.max(colHerald.actual, mwidth);
colHerald.measured = Math.max(colHerald.measured, mwidth);
amt = colHerald.actual - oldAmount;
colleft = Math.max(colleft - amt, 0);
} // end no fixed size objects
// Go through each object that spans multiple rows or columns
const additionalSpan = new go.Size();
const actualSizeRows: Array = [];
const actualSizeColumns: Array = [];
l = spanners.length;
if (l !== 0) {
// record the actual sizes of every row/column before measuring spanners
// because they will change during the loop and we want to use their 'before' values
for (let i = 0; i < lrow; i++) {
if (!rowcol[i]) continue;
lcol = rowcol[i].length; // column length in this row
const rowHerald = this.getRowDefinition(i);
actualSizeRows[i] = rowHerald.actual;
for (let j = 0; j < lcol; j++) {
// foreach column j in row i...
if (!rowcol[i][j]) continue;
const colHerald = this.getColumnDefinition(j);
actualSizeColumns[j] = colHerald.actual;
}
}
}
for (let i = 0; i < l; i++) {
const child = spanners[i];
const rowHerald = this.getRowDefinition(child.row);
const colHerald = this.getColumnDefinition(child.column);
// If there's a set column width/height we don't care about the given width/height
allowedSize.setTo(
Math.max(colHerald.minimum, Math.min(width, colHerald.maximum)),
Math.max(rowHerald.minimum, Math.min(height, rowHerald.maximum))
);
// If it is a spanner and has a fill:
const stretch = this.getEffectiveTableStretch(child, rowHerald, colHerald);
switch (stretch) {
case go.Stretch.Fill:
if (actualSizeColumns[colHerald.index] !== 0) {
allowedSize.width = Math.min(allowedSize.width, actualSizeColumns[colHerald.index]);
}
if (actualSizeRows[rowHerald.index] !== 0) {
allowedSize.height = Math.min(allowedSize.height, actualSizeRows[rowHerald.index]);
}
break;
case go.Stretch.Horizontal:
if (actualSizeColumns[colHerald.index] !== 0) {
allowedSize.width = Math.min(allowedSize.width, actualSizeColumns[colHerald.index]);
}
break;
case go.Stretch.Vertical:
if (actualSizeRows[rowHerald.index] !== 0) {
allowedSize.height = Math.min(allowedSize.height, actualSizeRows[rowHerald.index]);
}
break;
}
// If there's a set column width/height we don't care about any of the above:
if (isFinite(colHerald.width)) allowedSize.width = colHerald.width;
if (isFinite(rowHerald.height)) allowedSize.height = rowHerald.height;
// take into account rowSpan and columnSpan
let def = this.getRowDefinition(child.row);
additionalSpan.setTo(0, 0);
for (let n = 1; n < child.rowSpan; n++) {
if (child.row + n >= this.rowCount) break; // if the row exists at all
def = this.getRowDefinition(child.row + n);
amt = 0;
if (stretch === go.Stretch.Fill || stretch === go.Stretch.Vertical) {
amt = Math.max(
def.minimum,
actualSizeRows[child.row + n] === 0
? def.maximum
: Math.min(actualSizeRows[child.row + n], def.maximum)
);
} else {
amt = Math.max(
def.minimum,
isNaN(def.height) ? def.maximum : Math.min(def.height, def.maximum)
);
}
additionalSpan.height += amt;
}
for (let n = 1; n < child.columnSpan; n++) {
if (child.column + n >= this.columnCount) break; // if the col exists at all
def = this.getColumnDefinition(child.column + n);
amt = 0;
if (stretch === go.Stretch.Fill || stretch === go.Stretch.Horizontal) {
amt = Math.max(
def.minimum,
actualSizeColumns[child.column + n] === 0
? def.maximum
: Math.min(actualSizeColumns[child.column + n], def.maximum)
);
} else {
amt = Math.max(
def.minimum,
isNaN(def.width) ? def.maximum : Math.min(def.width, def.maximum)
);
}
additionalSpan.width += amt;
}
allowedSize.width += additionalSpan.width;
allowedSize.height += additionalSpan.height;
const marg = child.margin as go.Margin;
const margw = marg.right + marg.left;
const margh = marg.top + marg.bottom;
const m = this.getLayoutBounds(child);
const mwidth = Math.max(m.width + margw, 0);
const mheight = Math.max(m.height + margh, 0);
let totalRow = 0.0;
for (let n = 0; n < child.rowSpan; n++) {
if (child.row + n >= this.rowCount) break; // if the row exists at all
def = this.getRowDefinition(child.row + n);
totalRow += def.total || 0;
}
// def is the last row definition
if (totalRow < mheight) {
let roomLeft = mheight - totalRow;
while (roomLeft > 0) {
// Add the extra to the first row that allows us to
const act = def.actual || 0;
if (isNaN(def.height) && def.maximum > act) {
def.actual = Math.min(def.maximum, act + roomLeft);
if (def.actual !== act) roomLeft -= def.actual - act;
}
if (def.index - 1 === -1) break;
def = this.getRowDefinition(def.index - 1);
}
}
let totalCol = 0.0;
for (let n = 0; n < child.columnSpan; n++) {
if (child.column + n >= this.columnCount) break; // if the col exists at all
def = this.getColumnDefinition(child.column + n);
totalCol += def.total || 0;
}
// def is the last col definition
if (totalCol < mwidth) {
let roomLeft = mwidth - totalCol;
while (roomLeft > 0) {
// Add the extra to the first row that allows us to
const act = def.actual || 0;
if (isNaN(def.width) && def.maximum > act) {
def.actual = Math.min(def.maximum, act + roomLeft);
if (def.actual !== act) roomLeft -= def.actual - act;
}
if (def.index - 1 === -1) break;
def = this.getColumnDefinition(def.index - 1);
}
}
} // end spanning objects
l = this.columnCount;
for (let i = 0; i < l; i++) {
if (this._colDefs[i] === undefined) continue;
const def = this.getColumnDefinition(i);
def.position = union.width;
if (def.actual !== 0) {
union.width += def.actual;
union.width += def.computeEffectiveSpacing();
}
}
l = this.rowCount;
for (let i = 0; i < l; i++) {
if (this._rowDefs[i] === undefined) continue;
const def = this.getRowDefinition(i);
def.position = union.height;
if (def.actual !== 0) {
union.height += def.actual;
union.height += def.computeEffectiveSpacing();
}
}
// save these for arrange (destroy them or not? Possibly needed for drawing spacers)
return rowcol;
} // end measureTable
/**
* @hidden @internal
*/
arrangeTable(children: go.List, union: go.Size, rowcol: Array>): void {
const originx = this.arrangementOrigin.x;
const originy = this.arrangementOrigin.y;
let x = 0.0;
let y = 0.0;
const lrow = (rowcol as any).length; // number of rows
let lcol = 0;
for (let i = 0; i < lrow; i++) {
if (!(rowcol as any)[i]) continue;
lcol = Math.max(lcol, (rowcol as any)[i].length); // column length in this row
}
let firstRow = 0;
let firstColumn = 0;
let ll = this.columnCount;
for (let i = 0; i < ll; i++) {
if (this._colDefs[i] === undefined) continue;
firstColumn = i;
break;
}
ll = this.rowCount;
for (let i = 0; i < ll; i++) {
if (this._rowDefs[i] === undefined) continue;
firstRow = i;
break;
}
const additionalSpan = new go.Size();
// Find cell space and arrange objects:
for (let i = 0; i < lrow; i++) {
if (!rowcol[i]) continue;
lcol = rowcol[i].length; // column length in this row
const rowHerald = this.getRowDefinition(i);
y = originy + rowHerald.position + rowHerald.computeEffectiveSpacingTop(firstRow);
for (let j = 0; j < lcol; j++) {
// foreach column j in row i...
if (!rowcol[i][j]) continue;
const colHerald = this.getColumnDefinition(j);
x = originx + colHerald.position + colHerald.computeEffectiveSpacingTop(firstColumn);
const cell = rowcol[i][j];
const len = cell.length;
for (let k = 0; k < len; k++) {
// foreach element in cell
const child = cell[k];
// add to layoutWidth/Height any additional span
additionalSpan.setTo(0, 0);
for (let n = 1; n < child.rowSpan; n++) {
// if the row exists at all
if (i + n >= this.rowCount) break;
const rh = this.getRowDefinition(i + n);
additionalSpan.height += rh.total;
}
for (let n = 1; n < child.columnSpan; n++) {
// if the col exists at all
if (j + n >= this.columnCount) break;
const ch = this.getColumnDefinition(j + n);
additionalSpan.width += ch.total;
}
// Construct containing rect (cell):
// total width and height of the cell that an object could possibly be created in
const colwidth = colHerald.actual + additionalSpan.width;
const rowheight = rowHerald.actual + additionalSpan.height;
// construct a rect that represents the total cell size allowed for this object
const ar = new go.Rect();
ar.x = x;
ar.y = y;
ar.width = colwidth;
ar.height = rowheight;
// Construct alignment:
let align = child.alignment;
let alignx = 0.0;
let aligny = 0.0;
let alignoffsetX = 0.0;
let alignoffsetY = 0.0;
if (align.isDefault()) {
align = this.defaultAlignment;
if (!align.isSpot()) align = go.Spot.Center;
alignx = align.x;
aligny = align.y;
alignoffsetX = align.offsetX;
alignoffsetY = align.offsetY;
const ca = colHerald.alignment;
const ra = rowHerald.alignment;
if (ca.isSpot()) {
alignx = ca.x;
alignoffsetX = ca.offsetX;
}
if (ra.isSpot()) {
aligny = ra.y;
alignoffsetY = ra.offsetY;
}
} else {
alignx = align.x;
aligny = align.y;
alignoffsetX = align.offsetX;
alignoffsetY = align.offsetY;
}
// same as if (!align.isSpot()) align = go.Spot.Center;
if (isNaN(alignx) || isNaN(aligny)) {
alignx = 0.5;
aligny = 0.5;
alignoffsetX = 0;
alignoffsetY = 0;
}
let width = 0.0;
let height = 0.0;
const marg = child.margin;
const margw = marg.left + marg.right;
const margh = marg.top + marg.bottom;
const stretch = this.getEffectiveTableStretch(child, rowHerald, colHerald);
if (
/* isNaN(child.resizeObject.desiredSize.width) && */ stretch === go.Stretch.Fill ||
stretch === go.Stretch.Horizontal
) {
width = Math.max(colwidth - margw, 0);
} else {
width = this.getLayoutBounds(child).width;
}
if (
/* isNaN(child.resizeObject.desiredSize.height) && */ stretch === go.Stretch.Fill ||
stretch === go.Stretch.Vertical
) {
height = Math.max(rowheight - margh, 0);
} else {
height = this.getLayoutBounds(child).height;
}
// min and max override any stretch values
const max = child.maxSize;
const min = child.minSize;
width = Math.min(max.width, width);
height = Math.min(max.height, height);
width = Math.max(min.width, width);
height = Math.max(min.height, height);
const widthmarg = width + margw;
const heightmarg = height + margh;
ar.x += ar.width * alignx - widthmarg * alignx + alignoffsetX + marg.left;
ar.y += ar.height * aligny - heightmarg * aligny + alignoffsetY + marg.top;
child.moveTo(ar.x, ar.y);
if (stretch !== go.Stretch.None) {
child.resizeObject.desiredSize = new go.Size(width, height);
}
} // end cell
} // end col
} // end row
} // end arrangeTable
} // end TableLayout class