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