UNPKG

35.5 kBPlain TextView Raw
1/*
2* Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
3*/
4
5/*
6* This is an extension and not part of the main GoJS library.
7* Note that the API for this class may change with any version, even point releases.
8* If you intend to use an extension in production, you should copy the code to your own source directory.
9* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
10* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
11*/
12
13import * as go from '../release/go-module.js';
14
15/**
16 * This {@link Layout} positions non-Link Parts into a table according to the values of
17 * {@link GraphObject#row}, {@link GraphObject#column}, {@link GraphObject#rowSpan}, {@link GraphObject#columnSpan},
18 * {@link GraphObject#alignment}, {@link GraphObject#stretch}.
19 * If the value of GraphObject.stretch is not {@link GraphObject.None}, the Part will be sized
20 * according to the available space in the cell(s).
21 *
22 * You can specify constraints for whole rows or columns by calling
23 * {@link #getRowDefinition} or {@link #getColumnDefinition} and setting one of the following properties:
24 * {@link RowColumnDefinition#alignment}, {@link RowColumnDefinition#height}, {@link RowColumnDefinition#width},
25 * {@link RowColumnDefinition#maximum}, {@link RowColumnDefinition#minimum}, {@link RowColumnDefinition#stretch}.
26 *
27 * The {@link #defaultAlignment} and {@link #defaultStretch} properties apply to all parts if not specified
28 * on the individual Part or in the corresponding row or column definition.
29 *
30 * At the current time, there is no support for separator lines
31 * ({@link RowColumnDefinition#separatorStroke}, {@link RowColumnDefinition#separatorStrokeWidth},
32 * and {@link RowColumnDefinition#separatorDashArray} properties)
33 * nor background ({@link RowColumnDefinition#background} and {@link RowColumnDefinition#coversSeparators} properties).
34 * There is no support for {@link RowColumnDefinition#sizing}, either.
35 *
36 * If you want to experiment with this extension, try the <a href="../../extensionsJSM/Table.html">Table Layout</a> sample.
37 * @category Layout Extension
38 */
39export class TableLayout extends go.Layout {
40 private _defaultAlignment: go.Spot = go.Spot.Default;
41 private _defaultStretch: go.EnumValue = go.GraphObject.Default;
42 private _rowDefs: any = [];
43 private _colDefs: any = [];
44
45 /**
46 * Gets or sets the alignment to use by default for Parts in rows (vertically) and in columns (horizontally).
47 *
48 * The default value is {@link Spot.Default}.
49 * Setting this property does not raise any events.
50 */
51 get defaultAlignment(): go.Spot { return this._defaultAlignment; }
52 set defaultAlignment(val: go.Spot) { this._defaultAlignment = val; }
53
54 /**
55 * Gets or sets whether Parts should be stretched in rows (vertically) and in columns (horizontally).
56 *
57 * The default value is {@link GraphObject.Default}.
58 * Setting this property does not raise any events.
59 */
60 get defaultStretch(): go.EnumValue { return this._defaultStretch; }
61 set defaultStretch(val: go.EnumValue) { this._defaultStretch = val; }
62
63 /**
64 * This read-only property returns the number of rows in this TableLayout.
65 * This value is only valid after the layout has been performed.
66 */
67 get rowCount(): number { return this._rowDefs.length; }
68
69 /**
70 * This read-only property returns the number of columns in this TableLayout.
71 * This value is only valid after the layout has been performed.
72 */
73 get columnCount(): number { return this._colDefs.length; }
74
75 /**
76 * Copies properties to a cloned Layout.
77 */
78 public cloneProtected(copy: this): void {
79 super.cloneProtected(copy);
80 copy._defaultAlignment = this._defaultAlignment;
81 copy._defaultStretch = this._defaultStretch;
82 for (let i = 0; i < this._rowDefs.length; i++) {
83 const def = this._rowDefs[i];
84 copy._rowDefs.push(def !== undefined ? def.copy() : def);
85 }
86 for (let i = 0; i < this._colDefs.length; i++) {
87 const def = this._colDefs[i];
88 copy._colDefs.push(def !== undefined ? def.copy() : def);
89 }
90 }
91
92 /**
93 * Gets the {@link RowColumnDefinition} for a particular row in this TableLayout.
94 * If you ask for the definition of a row at or beyond the {@link #rowCount},
95 * it will automatically create one and return it.
96 * @param {number} idx the non-negative zero-based integer row index.
97 * @return {RowColumnDefinition}
98 */
99 public getRowDefinition(idx: number): go.RowColumnDefinition {
100 if (idx < 0) throw new Error('Row index must be non-negative, not: ' + idx);
101 idx = Math.round(idx);
102 const defs = this._rowDefs;
103
104 let d = defs[idx];
105 if (d === undefined) {
106 d = new go.RowColumnDefinition();
107 // .panel remains null
108 d.isRow = true;
109 d.index = idx;
110 defs[idx] = d;
111 }
112 return d;
113 }
114
115 /**
116 * Returns the row at a given y-coordinate in document coordinates.
117 * This information is only valid when this layout has been performed and {@link Layout#isValidLayout} is true.
118 *
119 * If the point is above row 0, this method returns -1.
120 * If the point below the last row, this returns the last row + 1.
121 * @param {number} y
122 * @return {number} a zero-based integer
123 * @see {@link #findColumnForDocumentX}
124 */
125 public findRowForDocumentY(y: number): number {
126 y -= this.arrangementOrigin.y;
127 if (y < 0) return -1;
128 let total = 0.0;
129 const it = this._rowDefs;
130 const l = it.length;
131 for (let i = 0; i < l; i++) {
132 const def = it[i];
133 if (def === undefined) continue;
134 total += def.total;
135 if (y < total) {
136 return i;
137 }
138 }
139 return l;
140 }
141
142 /**
143 * Gets the {@link RowColumnDefinition} for a particular column in this TableLayout.
144 * If you ask for the definition of a column at or beyond the {@link #columnCount},
145 * it will automatically create one and return it.
146 * @param {number} idx the non-negative zero-based integer column index.
147 * @return {RowColumnDefinition}
148 */
149 public getColumnDefinition(idx: number): go.RowColumnDefinition {
150 if (idx < 0) throw new Error('Column index must be non-negative, not: ' + idx);
151 idx = Math.round(idx);
152 const defs = this._colDefs;
153
154 let d = defs[idx];
155 if (d === undefined) {
156 d = new go.RowColumnDefinition();
157 // .panel remains null
158 d.isRow = false;
159 d.index = idx;
160 defs[idx] = d;
161 }
162 return d;
163 }
164
165 /**
166 * Returns the cell at a given x-coordinate in document coordinates.
167 * This information is only valid when this layout has been performed and {@link Layout#isValidLayout} is true.
168 *
169 * If the point is to left of the column 0, this method returns -1.
170 * If the point to to the right of the last column, this returns the last column + 1.
171 * @param {number} x
172 * @return {number} a zero-based integer
173 * @see {@link #findRowForDocumentY}
174 */
175 public findColumnForDocumentX(x: number): number {
176 x -= this.arrangementOrigin.x;
177 if (x < 0) return -1;
178 let total = 0.0;
179 const it = this._colDefs;
180 const l = it.length;
181 for (let i = 0; i < l; i++) {
182 const def = it[i];
183 if (def === undefined) continue;
184 total += def.total;
185 if (x < total) {
186 return i;
187 }
188 }
189 return l;
190 }
191
192 /**
193 * @hidden @internal
194 * Only ever called from TableLayout's measure and arrange
195 */
196 private getEffectiveTableStretch(child: go.Part, row: go.RowColumnDefinition, col: go.RowColumnDefinition): go.EnumValue {
197 const effectivestretch = child.stretch;
198 if (effectivestretch !== go.GraphObject.Default) return effectivestretch;
199 // which directions are we stretching?
200 // undefined = default
201 let horizontal;
202 let vertical;
203 switch (row.stretch) {
204 case go.GraphObject.Default:
205 case go.GraphObject.Horizontal: break;
206 case go.GraphObject.Vertical: vertical = true; break;
207 case go.GraphObject.Fill: vertical = true; break;
208 }
209 switch (col.stretch) {
210 case go.GraphObject.Default:
211 case go.GraphObject.Vertical: break;
212 case go.GraphObject.Horizontal: horizontal = true; break;
213 case go.GraphObject.Fill: horizontal = true; break;
214 }
215
216 const str = this.defaultStretch;
217 if (horizontal === undefined && (str === go.GraphObject.Horizontal || str === go.GraphObject.Fill)) {
218 horizontal = true;
219 } else {
220 horizontal = false;
221 }
222 if (vertical === undefined && (str === go.GraphObject.Vertical || str === go.GraphObject.Fill)) {
223 vertical = true;
224 } else {
225 vertical = false;
226 }
227 if (horizontal === true && vertical === true) return go.GraphObject.Fill;
228 if (horizontal === true) return go.GraphObject.Horizontal;
229 if (vertical === true) return go.GraphObject.Vertical;
230 return go.GraphObject.None; // Everything else is none by default
231 }
232
233 /**
234 * This method performs the measuring and arranging of the table, assiging positions to each part.
235 * @param {Iterable.<Part>} coll A collection of {@link Part}s.
236 */
237 public doLayout(coll: go.Iterable<go.Part>): void {
238 this.arrangementOrigin = this.initialOrigin(this.arrangementOrigin);
239 // put all eligible Parts that are not Links into an Array
240 const parts = new go.List<go.Part>();
241 this.collectParts(coll).each((p) => {
242 if (!(p instanceof go.Link)) {
243 parts.add(p);
244 }
245 });
246
247 if (this.diagram !== null) {
248 this.diagram.startTransaction('TableLayout');
249 const union = new go.Size();
250 // this calls .beforeMeasure(parts, rowcol)
251 const rowcol = this.measureTable(Infinity, Infinity, parts, union, 0, 0);
252 this.arrangeTable(parts, union, rowcol);
253 this.afterArrange(parts, rowcol);
254 this.diagram.commitTransaction('TableLayout');
255 }
256 }
257
258 /**
259 * Override this method in order to perform some operations before measuring.
260 * By default this method does nothing.
261 * @expose
262 */
263 protected beforeMeasure(parts: go.List<go.Part>, rowcol: Array<Array<Array<any>>>): void { }
264
265 /**
266 * Override this method in order to perform some operations after arranging.
267 * By default this method does nothing.
268 * @expose
269 */
270 protected afterArrange(parts: go.List<go.Part>, rowcol: Array<Array<Array<any>>>): void { }
271
272 /**
273 * @hidden @internal
274 */
275 public measureTable(width: number, height: number, children: go.List<go.Part>, union: go.Size, minw: number, minh: number): Array<Array<any>> {
276 let l = children.length;
277 // Make the array that holds [rows][cols] of the table
278 const rowcol: Array<Array<any>> = []; // saved (so no temp array) starts as an array of rows, will end up [row][col][cell]
279 for (let i = 0; i < l; i++) {
280 const child = children.elt(i);
281 if (!rowcol[child.row]) {
282 rowcol[child.row] = []; // make new column for this row
283 }
284 if (!rowcol[child.row][child.column]) {
285 rowcol[child.row][child.column] = []; // new list for this cell
286 }
287 rowcol[child.row][child.column].push(child); // push child into right cell
288 }
289
290 this.beforeMeasure(children, rowcol);
291
292 // Reset the row/col definitions because the ones from last measure are irrelevant
293 const resetCols = []; // keep track of which columns we've already reset
294
295 // Objects that span multiple columns and
296 const spanners = [];
297 const nosize = [];
298 // These hashes are used to tally the number of rows and columns that do not have a size
299 const nosizeCols = { count: 0 };
300 const nosizeRows = { count: 0 };
301
302 let colleft = width;
303 let rowleft = height;
304
305 let defs = this._rowDefs;
306 l = defs.length;
307 for (let i = 0; i < l; i++) {
308 const def = defs[i];
309 if (def !== undefined) def.actual = 0;
310 }
311
312 defs = this._colDefs;
313 l = defs.length;
314 for (let i = 0; i < l; i++) {
315 const def = defs[i];
316 if (def !== undefined) def.actual = 0;
317 }
318
319 let lrow = rowcol.length; // number of rows
320 let lcol = 0;
321 for (let i = 0; i < lrow; i++) {
322 if (!rowcol[i]) continue;
323 lcol = Math.max(lcol, rowcol[i].length); // column length in this row
324 }
325
326 // Go through each cell (first pass)
327 let amt = 0.0;
328 lrow = rowcol.length; // number of rows
329 for (let i = 0; i < lrow; i++) {
330 if (!rowcol[i]) continue;
331 lcol = rowcol[i].length; // column length in this row
332 const rowHerald = this.getRowDefinition(i);
333 rowHerald.measured = 0; // Reset rows (only on first pass)
334 for (let j = 0; j < lcol; j++) {
335 // foreach column j in row i...
336 if (!rowcol[i][j]) continue;
337 const colHerald = this.getColumnDefinition(j);
338 if (resetCols[j] === undefined) { // make sure we only reset these once
339 colHerald.measured = 0;
340 resetCols[j] = true;
341 }
342
343 const cell = rowcol[i][j];
344 const len = cell.length;
345 for (let k = 0; k < len; k++) {
346 // foreach element in cell, measure
347 const child: go.Part = cell[k];
348
349 // Skip children that span more than one row or column or do not have a set size
350 const spanner = (child.rowSpan > 1 || child.columnSpan > 1);
351 if (spanner) {
352 spanners.push(child);
353 // We used to not measure spanners twice, but now we do
354 // The reason is that there may be a row whose size
355 // is dictated by an object with columnSpan 2+ and vice versa
356
357 // continue;
358 }
359
360 const marg = child.margin as go.Margin;
361 const margw = marg.right + marg.left;
362 const margh = marg.top + marg.bottom;
363
364 const stretch = this.getEffectiveTableStretch(child, rowHerald, colHerald);
365 const dsize = child.resizeObject.desiredSize;
366 const realwidth = !(isNaN(dsize.width));
367 const realheight = !(isNaN(dsize.height));
368 const realsize = realwidth && realheight;
369 if (!spanner && stretch !== go.GraphObject.None && !realsize) {
370 if ((nosizeCols as any)[j] === undefined && (stretch === go.GraphObject.Fill || stretch === go.GraphObject.Horizontal)) {
371 (nosizeCols as any)[j] = -1; nosizeCols.count++;
372 }
373 if ((nosizeRows as any)[i] === undefined && (stretch === go.GraphObject.Fill || stretch === go.GraphObject.Vertical)) {
374 (nosizeRows as any)[i] = -1; nosizeRows.count++;
375 }
376 nosize.push(child);
377 }
378
379 if (stretch !== go.GraphObject.None) {
380 const unrestrictedSize = new go.Size(NaN, NaN);
381 // if (stretch !== go.GraphObject.Horizontal) unrestrictedSize.height = rowHerald.minimum;
382 // if (stretch !== go.GraphObject.Vertical) unrestrictedSize.width = colHerald.minimum;
383 // ??? allow resizing during measure phase
384 child.resizeObject.desiredSize = unrestrictedSize;
385 child.ensureBounds();
386 }
387
388 const m = this.getLayoutBounds(child);
389 const mwidth = Math.max(m.width + margw, 0);
390 const mheight = Math.max(m.height + margh, 0);
391
392 // Make sure the heralds have the right layout size
393 // the row/column should use the largest measured size of any
394 // GraphObject contained, constrained by mins and maxes
395 if (child.rowSpan === 1 && (realheight || stretch === go.GraphObject.None || stretch === go.GraphObject.Horizontal)) {
396 const def = this.getRowDefinition(i);
397 amt = Math.max(mheight - def.actual, 0);
398 if (amt > rowleft) amt = rowleft;
399 def.measured = def.measured + amt;
400 def.actual = def.actual + amt;
401 rowleft = Math.max(rowleft - amt, 0);
402 }
403
404 if (child.columnSpan === 1 && (realwidth || stretch === go.GraphObject.None || stretch === go.GraphObject.Vertical)) {
405 const def = this.getColumnDefinition(j);
406 amt = Math.max(mwidth - def.actual, 0);
407 if (amt > colleft) amt = colleft;
408 def.measured = def.measured + amt;
409 def.actual = def.actual + amt;
410 colleft = Math.max(colleft - amt, 0);
411 }
412
413 } // end cell
414 } // end col
415 } // end row
416
417 // For objects of no desired size we allocate what is left as we go,
418 // or else what is already in the column
419 let totalColWidth = 0.0;
420 let totalRowHeight = 0.0;
421 l = this.columnCount;
422 for (let i = 0; i < l; i++) {
423 if (this._colDefs[i] === undefined) continue;
424 totalColWidth += this.getColumnDefinition(i).measured;
425 }
426 l = this.rowCount;
427 for (let i = 0; i < l; i++) {
428 if (this._rowDefs[i] === undefined) continue;
429 totalRowHeight += this.getRowDefinition(i).measured;
430 }
431 colleft = Math.max(width - totalColWidth, 0);
432 rowleft = Math.max(height - totalRowHeight, 0);
433 const originalrowleft = rowleft;
434 const originalcolleft = colleft;
435
436 // Determine column sizes for the yet-to-be-sized columns
437 l = nosize.length;
438 for (let i = 0; i < l; i++) {
439 const child = nosize[i];
440 const rowHerald = this.getRowDefinition(child.row);
441 const colHerald = this.getColumnDefinition(child.column);
442 // We want to gather the largest difference between desired and expected col/row sizes
443 const mb = this.getLayoutBounds(child);
444 const marg = child.margin as go.Margin;
445 const margw = marg.right + marg.left;
446 const margh = marg.top + marg.bottom;
447
448 if (colHerald.measured === 0 && (nosizeCols as any)[child.column] !== undefined) {
449 (nosizeCols as any)[child.column] = Math.max(mb.width + margw, (nosizeCols as any)[child.column]);
450 } else {
451 (nosizeCols as any)[child.column] = null; // obey the column herald
452 }
453 if (rowHerald.measured === 0 && (nosizeRows as any)[child.row] !== undefined) {
454 (nosizeRows as any)[child.row] = Math.max(mb.height + margh, (nosizeRows as any)[child.row]);
455 } else {
456 (nosizeRows as any)[child.row] = null; // obey the row herald
457 }
458 }
459 // we now have the size that all these columns prefer to be
460 // we also have the amount left over
461 let desiredRowTotal = 0.0;
462 let desiredColTotal = 0.0;
463 for (const i in nosizeRows) { if (i !== 'count') desiredRowTotal += (nosizeRows as any)[i]; }
464 for (const i in nosizeCols) { if (i !== 'count') desiredColTotal += (nosizeCols as any)[i]; }
465
466 const allowedSize = new go.Size(); // used in stretch and span loops
467
468 // Deal with objects that have a stretch
469 for (let i = 0; i < l; i++) {
470 const child = nosize[i];
471 const rowHerald = this.getRowDefinition(child.row);
472 const colHerald = this.getColumnDefinition(child.column);
473
474 let w = 0.0;
475 if (isFinite(colHerald.width)) {
476 w = colHerald.width;
477 } else {
478 if (isFinite(colleft) && (nosizeCols as any)[child.column] !== null) {
479 if (desiredColTotal === 0) w = colHerald.actual + colleft;
480 else w = /*colHerald.actual +*/ (((nosizeCols as any)[child.column] / desiredColTotal) * originalcolleft);
481 } else {
482 // Only use colHerald.actual if it was nonzero before this loop
483 if ((nosizeCols as any)[child.column] !== null) w = colleft;
484 else w = colHerald.actual || colleft;
485 // w = nosizeCols[child.column] || colleft; // Older, less correct way
486 }
487 w = Math.max(0, w - colHerald.computeEffectiveSpacing());
488 }
489 let h = 0.0;
490 if (isFinite(rowHerald.height)) {
491 h = rowHerald.height;
492 } else {
493 if (isFinite(rowleft) && (nosizeRows as any)[child.row] !== null) {
494 if (desiredRowTotal === 0) h = rowHerald.actual + rowleft;
495 else h = /*rowHerald.actual +*/ (((nosizeRows as any)[child.row] / desiredRowTotal) * originalrowleft);
496 } else {
497 // Only use rowHerald.actual if it was nonzero before this loop
498 if ((nosizeRows as any)[child.row] !== null) h = rowleft;
499 else h = rowHerald.actual || rowleft;
500 // h = nosizeRows[child.row] || rowleft; // Older, less correct way
501 }
502 h = Math.max(0, h - rowHerald.computeEffectiveSpacing());
503 }
504
505 allowedSize.setTo(
506 Math.max(colHerald.minimum, Math.min(w, colHerald.maximum)),
507 Math.max(rowHerald.minimum, Math.min(h, rowHerald.maximum)));
508
509 // Which way do we care about fill:
510 const stretch = this.getEffectiveTableStretch(child, rowHerald, colHerald);
511 // This used to set allowedSize height/width to Infinity,
512 // but we can only set it to the current row/column space, plus rowleft/colleft values, at most.
513 switch (stretch) {
514 case go.GraphObject.Horizontal: // H stretch means it can be as large as its wants vertically
515 allowedSize.height = Math.max(allowedSize.height, rowHerald.actual + rowleft);
516 break;
517 case go.GraphObject.Vertical: // vice versa
518 allowedSize.width = Math.max(allowedSize.width, colHerald.actual + colleft);
519 break;
520 }
521
522 const marg = child.margin as go.Margin;
523 const margw = marg.right + marg.left;
524 const margh = marg.top + marg.bottom;
525
526 const m = this.getLayoutBounds(child);
527 let mwidth = Math.max(m.width + margw, 0);
528 let mheight = Math.max(m.height + margh, 0);
529 if (isFinite(colleft)) mwidth = Math.min(mwidth, allowedSize.width);
530 if (isFinite(rowleft)) mheight = Math.min(mheight, allowedSize.height);
531
532 let oldAmount = 0.0;
533
534 oldAmount = rowHerald.actual;
535 rowHerald.actual = Math.max(rowHerald.actual, mheight);
536 rowHerald.measured = Math.max(rowHerald.measured, mheight);
537 amt = rowHerald.actual - oldAmount;
538 rowleft = Math.max(rowleft - amt, 0);
539
540 oldAmount = colHerald.actual;
541 colHerald.actual = Math.max(colHerald.actual, mwidth);
542 colHerald.measured = Math.max(colHerald.measured, mwidth);
543 amt = colHerald.actual - oldAmount;
544 colleft = Math.max(colleft - amt, 0);
545 } // end no fixed size objects
546
547 // Go through each object that spans multiple rows or columns
548 const additionalSpan = new go.Size();
549 const actualSizeRows: Array<number> = [];
550 const actualSizeColumns: Array<number> = [];
551 l = spanners.length;
552 if (l !== 0) {
553 // record the actual sizes of every row/column before measuring spanners
554 // because they will change during the loop and we want to use their 'before' values
555 for (let i = 0; i < lrow; i++) {
556 if (!rowcol[i]) continue;
557 lcol = rowcol[i].length; // column length in this row
558 const rowHerald = this.getRowDefinition(i);
559 actualSizeRows[i] = rowHerald.actual;
560 for (let j = 0; j < lcol; j++) {
561 // foreach column j in row i...
562 if (!rowcol[i][j]) continue;
563 const colHerald = this.getColumnDefinition(j);
564 actualSizeColumns[j] = colHerald.actual;
565 }
566 }
567 }
568 for (let i = 0; i < l; i++) {
569 const child = spanners[i];
570 const rowHerald = this.getRowDefinition(child.row);
571 const colHerald = this.getColumnDefinition(child.column);
572
573 // If there's a set column width/height we don't care about the given width/height
574 allowedSize.setTo(
575 Math.max(colHerald.minimum, Math.min(width, colHerald.maximum)),
576 Math.max(rowHerald.minimum, Math.min(height, rowHerald.maximum)));
577
578 // If it is a spanner and has a fill:
579 const stretch = this.getEffectiveTableStretch(child, rowHerald, colHerald);
580 switch (stretch) {
581 case go.GraphObject.Fill:
582 if (actualSizeColumns[colHerald.index] !== 0) allowedSize.width = Math.min(allowedSize.width, actualSizeColumns[colHerald.index]);
583 if (actualSizeRows[rowHerald.index] !== 0) allowedSize.height = Math.min(allowedSize.height, actualSizeRows[rowHerald.index]);
584 break;
585 case go.GraphObject.Horizontal:
586 if (actualSizeColumns[colHerald.index] !== 0) allowedSize.width = Math.min(allowedSize.width, actualSizeColumns[colHerald.index]);
587 break;
588 case go.GraphObject.Vertical:
589 if (actualSizeRows[rowHerald.index] !== 0) allowedSize.height = Math.min(allowedSize.height, actualSizeRows[rowHerald.index]);
590 break;
591 }
592 // If there's a set column width/height we don't care about any of the above:
593 if (isFinite(colHerald.width)) allowedSize.width = colHerald.width;
594 if (isFinite(rowHerald.height)) allowedSize.height = rowHerald.height;
595
596 // take into account rowSpan and columnSpan
597 let def = this.getRowDefinition(child.row);
598 additionalSpan.setTo(0, 0);
599 for (let n = 1; n < child.rowSpan; n++) {
600 if (child.row + n >= this.rowCount) break; // if the row exists at all
601 def = this.getRowDefinition(child.row + n);
602 amt = 0;
603 if (stretch === go.GraphObject.Fill || stretch === go.GraphObject.Vertical) {
604 amt = Math.max(def.minimum, (actualSizeRows[child.row + n] === 0) ? def.maximum : Math.min(actualSizeRows[child.row + n], def.maximum));
605 } else {
606 amt = Math.max(def.minimum, isNaN(def.height) ? def.maximum : Math.min(def.height, def.maximum));
607 }
608 additionalSpan.height += amt;
609 }
610 for (let n = 1; n < child.columnSpan; n++) {
611 if (child.column + n >= this.columnCount) break; // if the col exists at all
612 def = this.getColumnDefinition(child.column + n);
613 amt = 0;
614 if (stretch === go.GraphObject.Fill || stretch === go.GraphObject.Horizontal) {
615 amt = Math.max(def.minimum, (actualSizeColumns[child.column + n] === 0) ? def.maximum : Math.min(actualSizeColumns[child.column + n], def.maximum));
616 } else {
617 amt = Math.max(def.minimum, isNaN(def.width) ? def.maximum : Math.min(def.width, def.maximum));
618 }
619 additionalSpan.width += amt;
620 }
621 allowedSize.width += additionalSpan.width;
622 allowedSize.height += additionalSpan.height;
623
624 const marg = child.margin as go.Margin;
625 const margw = marg.right + marg.left;
626 const margh = marg.top + marg.bottom;
627 const m = this.getLayoutBounds(child);
628 const mwidth = Math.max(m.width + margw, 0);
629 const mheight = Math.max(m.height + margh, 0);
630
631 let totalRow = 0.0;
632 for (let n = 0; n < child.rowSpan; n++) {
633 if (child.row + n >= this.rowCount) break; // if the row exists at all
634 def = this.getRowDefinition(child.row + n);
635 totalRow += def.total || 0;
636 }
637 // def is the last row definition
638 if (totalRow < mheight) {
639 let roomLeft = mheight - totalRow;
640 while (roomLeft > 0) { // Add the extra to the first row that allows us to
641 const act = def.actual || 0;
642 if (isNaN(def.height) && def.maximum > act) {
643 def.actual = Math.min(def.maximum, act + roomLeft);
644 if (def.actual !== act) roomLeft -= def.actual - act;
645 }
646 if (def.index - 1 === -1) break;
647 def = this.getRowDefinition(def.index - 1);
648 }
649 }
650
651 let totalCol = 0.0;
652 for (let n = 0; n < child.columnSpan; n++) {
653 if (child.column + n >= this.columnCount) break; // if the col exists at all
654 def = this.getColumnDefinition(child.column + n);
655 totalCol += def.total || 0;
656 }
657 // def is the last col definition
658 if (totalCol < mwidth) {
659 let roomLeft = mwidth - totalCol;
660 while (roomLeft > 0) { // Add the extra to the first row that allows us to
661 const act = def.actual || 0;
662 if (isNaN(def.width) && def.maximum > act) {
663 def.actual = Math.min(def.maximum, act + roomLeft);
664 if (def.actual !== act) roomLeft -= def.actual - act;
665 }
666 if (def.index - 1 === -1) break;
667 def = this.getColumnDefinition(def.index - 1);
668 }
669 }
670 } // end spanning objects
671
672 l = this.columnCount;
673 for (let i = 0; i < l; i++) {
674 if (this._colDefs[i] === undefined) continue;
675 const def = this.getColumnDefinition(i);
676 def.position = union.width;
677 if (def.actual !== 0) {
678 union.width += def.actual;
679 union.width += def.computeEffectiveSpacing();
680 }
681 }
682
683 l = this.rowCount;
684 for (let i = 0; i < l; i++) {
685 if (this._rowDefs[i] === undefined) continue;
686 const def = this.getRowDefinition(i);
687 def.position = union.height;
688 if (def.actual !== 0) {
689 union.height += def.actual;
690 union.height += def.computeEffectiveSpacing();
691 }
692 }
693
694 // save these for arrange (destroy them or not? Possibly needed for drawing spacers)
695 return rowcol;
696 } // end measureTable
697
698
699 /**
700 * @hidden @internal
701 */
702 public arrangeTable(children: go.List<go.Part>, union: go.Size, rowcol: Array<Array<any>>): void {
703 const l = children.length;
704 const originx = this.arrangementOrigin.x;
705 const originy = this.arrangementOrigin.y;
706 let x = 0.0;
707 let y = 0.0;
708
709 const lrow = (rowcol as any).length; // number of rows
710 let lcol = 0;
711 for (let i = 0; i < lrow; i++) {
712 if (!(rowcol as any)[i]) continue;
713 lcol = Math.max(lcol, (rowcol as any)[i].length); // column length in this row
714 }
715
716 const additionalSpan = new go.Size();
717 // Find cell space and arrange objects:
718 for (let i = 0; i < lrow; i++) {
719 if (!rowcol[i]) continue;
720 lcol = rowcol[i].length; // column length in this row
721 const rowHerald = this.getRowDefinition(i);
722 y = originy + rowHerald.position + rowHerald.computeEffectiveSpacingTop();
723 for (let j = 0; j < lcol; j++) {
724 // foreach column j in row i...
725 if (!rowcol[i][j]) continue;
726 const colHerald = this.getColumnDefinition(j);
727 x = originx + colHerald.position + colHerald.computeEffectiveSpacingTop();
728 const cell = rowcol[i][j];
729 const len = cell.length;
730
731 for (let k = 0; k < len; k++) {
732 // foreach element in cell
733 const child = cell[k];
734
735 // add to layoutWidth/Height any additional span
736 additionalSpan.setTo(0, 0);
737
738 for (let n = 1; n < child.rowSpan; n++) {
739 // if the row exists at all
740 if (i + n >= this.rowCount) break;
741 const rh = this.getRowDefinition(i + n);
742 additionalSpan.height += rh.total;
743 }
744
745 for (let n = 1; n < child.columnSpan; n++) {
746 // if the col exists at all
747 if (j + n >= this.columnCount) break;
748 const ch = this.getColumnDefinition(j + n);
749 additionalSpan.width += ch.total;
750 }
751
752 // Construct containing rect (cell):
753
754 // total width and height of the cell that an object could possibly be created in
755 const colwidth = colHerald.actual + additionalSpan.width;
756 const rowheight = rowHerald.actual + additionalSpan.height;
757
758 // construct a rect that represents the total cell size allowed for this object
759 const ar = new go.Rect();
760 ar.x = x;
761 ar.y = y;
762 ar.width = colwidth;
763 ar.height = rowheight;
764
765 // Also keep them for clip values
766 const cellx = x;
767 const celly = y;
768 let cellw = colwidth;
769 let cellh = rowheight;
770 // Ending rows/col might have actual spaces that are larger than the remaining space
771 // Modify them for clipping regions
772 if (x + colwidth > union.width) cellw = Math.max(union.width - x, 0);
773 if (y + rowheight > union.height) cellh = Math.max(union.height - y, 0);
774
775
776 // Construct alignment:
777 let align = child.alignment;
778 let alignx = 0.0;
779 let aligny = 0.0;
780 let alignoffsetX = 0.0;
781 let alignoffsetY = 0.0;
782 if (align.isDefault()) {
783 align = this.defaultAlignment;
784 if (!align.isSpot()) align = go.Spot.Center;
785 alignx = align.x;
786 aligny = align.y;
787 alignoffsetX = align.offsetX;
788 alignoffsetY = align.offsetY;
789 const ca = colHerald.alignment;
790 const ra = rowHerald.alignment;
791 if (ca.isSpot()) {
792 alignx = ca.x;
793 alignoffsetX = ca.offsetX;
794 }
795 if (ra.isSpot()) {
796 aligny = ra.y;
797 alignoffsetY = ra.offsetY;
798 }
799 } else {
800 alignx = align.x;
801 aligny = align.y;
802 alignoffsetX = align.offsetX;
803 alignoffsetY = align.offsetY;
804 }
805
806 // same as if (!align.isSpot()) align = go.Spot.Center;
807 if (isNaN(alignx) || isNaN(aligny)) {
808 alignx = 0.5;
809 aligny = 0.5;
810 alignoffsetX = 0;
811 alignoffsetY = 0;
812 }
813
814 let width = 0.0;
815 let height = 0.0;
816
817 const marg = child.margin;
818 const margw = marg.left + marg.right;
819 const margh = marg.top + marg.bottom;
820 const stretch = this.getEffectiveTableStretch(child, rowHerald, colHerald);
821 if (/* isNaN(child.resizeObject.desiredSize.width) && */ (stretch === go.GraphObject.Fill || stretch === go.GraphObject.Horizontal)) {
822 width = Math.max(colwidth - margw, 0);
823 } else {
824 width = this.getLayoutBounds(child).width;
825 }
826 if (/* isNaN(child.resizeObject.desiredSize.height) && */ (stretch === go.GraphObject.Fill || stretch === go.GraphObject.Vertical)) {
827 height = Math.max(rowheight - margh, 0);
828 } else {
829 height = this.getLayoutBounds(child).height;
830 }
831
832 // min and max override any stretch values
833 const max = child.maxSize;
834 const min = child.minSize;
835 width = Math.min(max.width, width);
836 height = Math.min(max.height, height);
837 width = Math.max(min.width, width);
838 height = Math.max(min.height, height);
839
840 const widthmarg = width + margw;
841 const heightmarg = height + margh;
842
843 ar.x += (ar.width * alignx) - (widthmarg * alignx) + alignoffsetX + marg.left;
844 ar.y += (ar.height * aligny) - (heightmarg * aligny) + alignoffsetY + marg.top;
845
846 child.moveTo(ar.x, ar.y);
847 if (stretch !== go.GraphObject.None) {
848 child.resizeObject.desiredSize = new go.Size(width, height);
849 }
850
851 } // end cell
852 } // end col
853 } // end row
854 } // end arrangeTable
855
856} // end TableLayout class
857