UNPKG

12.6 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* A custom {@link PanelLayout} that arranges panel elements in rows or columns.
17* A typical use might be:
18* <pre>
19* $(go.Node,
20* ...
21* $(go.Panel, "Flow",
22* ... the elements to be laid out in rows with no space between them ...
23* )
24* ...
25* )
26* </pre>
27* A customized use might be:
28* <pre>
29* $(go.Node,
30* ...
31* $(go.Panel,
32* $(PanelLayoutFlow, { spacing: new go.Size(5, 5), direction: 90 }),
33* ... the elements to be laid out in columns ...
34* )
35* ...
36* )
37* </pre>
38*
39* The {@link #direction} property determines whether the elements are arranged in rows (if 0 or 180)
40* or in columns (if 90 or 270).
41*
42* Use the {@link #spacing} property to control how much space there is between elements in a row or column
43* as well as between rows or columns.
44*
45* This layout respects the {@link GraphObject#visible}, {@link GraphObject#stretch},
46* and {@link GraphObject#alignment} properties on each element, along with the Panel's
47* {@link Panel#defaultStretch}, {@link Panel#defaultAlignment}, and {@link Panel#padding} properties.
48*
49* If you want to experiment with this extension, try the <a href="../../extensionsJSM/PanelLayoutFlow.html">Flow PanelLayout</a> sample.
50* @category Layout Extension
51*/
52export class PanelLayoutFlow extends go.PanelLayout {
53 private _direction: number = 0; // only 0 or 180 (rows) or 90 or 270 (columns)
54 private _spacing: go.Size = new go.Size(0, 0); // space between elements and rows/columns
55 private _lineBreadths: Array<number> = []; // row height or column width, excluding spacing
56 private _lineLengths: Array<number> = []; // line length, excluding padding and external spacing
57
58 /**
59 * Constructs a PanelLayoutFlow that lays out elements in rows
60 * with no space between the elements or between the rows.
61 */
62 constructor() {
63 super();
64 this.name = "Flow";
65 }
66
67 /**
68 * Gets or sets the initial direction in which elements are laid out.
69 * The value must be 0 or 180, which results in rows, or 90 or 270, which results in columns.
70 *
71 * The default value is 0, resulting in rows that go rightward.
72 * A value of 90 results in columns that go downward.
73 *
74 * Setting this property does not notify about any changed event,
75 * nor does a change in value automatically cause the panel layout to be performed again.
76 */
77 get direction() { return this._direction; }
78 set direction(d: number) {
79 if (d !== 0 && d !== 90 && d !== 180 && d !== 270) throw new Error("bad direction for PanelLayoutFlow: " + d);
80 this._direction = d;
81 }
82
83 /**
84 * Gets or sets the space between adjacent elements in the panel and the space between adjacent rows or columns.
85 *
86 * The default value is (0, 0). The size is in the panel's coordinate system.
87 *
88 * Setting this property does not notify about any changed event,
89 * nor does a change in value automatically cause the panel layout to be performed again.
90 */
91 get spacing() { return this._spacing; }
92 set spacing(s: go.Size) { this._spacing = s; }
93
94 public measure(panel: go.Panel, width: number, height: number, elements: Array<go.GraphObject>, union: go.Rect, minw: number, minh: number): void {
95 this._lineBreadths = [];
96 this._lineLengths = [];
97 const pad = <go.Margin>panel.padding;
98 const wrapx = width + pad.left; // might be Infinity
99 const wrapy = height + pad.top;
100 let x = pad.left; // account for padding
101 let y = pad.top;
102 const xstart = x; // remember start
103 const ystart = y;
104 // assume that measuring is the same for either direction
105 if (this.direction === 0 || this.direction === 180) {
106 let maxx = x; // track total width
107 let rowh = 0; // compute row height
108 for (let i = 0; i < elements.length; i++) {
109 const elem = elements[i];
110 if (!elem.visible) continue;
111 this.measureElement(elem, Infinity, height, minw, minh);
112 const mb = elem.measuredBounds;
113 const marg = <go.Margin>elem.margin;
114 const gw = marg.left + mb.width + marg.right; // gross size including margins
115 const gh = marg.top + mb.height + marg.bottom;
116 if (x + gw > wrapx && i > 0) { // next row
117 this._lineBreadths.push(rowh); // remember previous row info
118 this._lineLengths.push(x - pad.left);
119 y += rowh + this.spacing.height; // advance x and y
120 if (y + gh <= wrapy) { // next row fits???
121 x = xstart + gw + this.spacing.width;
122 rowh = gh;
123 } else { // clipped, assume zero size
124 x = xstart;
125 rowh = 0;
126 break;
127 }
128 } else { // advance x
129 x += gw + this.spacing.width;
130 rowh = Math.max(rowh, gh);
131 }
132 maxx = Math.max(maxx, x);
133 }
134 this._lineBreadths.push(rowh);
135 this._lineLengths.push(x - pad.left);
136 union.width = Math.max(0, Math.min(maxx, wrapx) - pad.left); // don't add padding to union
137 union.height = Math.max(0, y + rowh - pad.top);
138 } else if (this.direction === 90 || this.direction === 270) {
139 let maxy = y;
140 let colw = 0;
141 for (let i = 0; i < elements.length; i++) {
142 const elem = elements[i];
143 if (!elem.visible) continue;
144 this.measureElement(elem, width, Infinity, minw, minh);
145 const mb = elem.measuredBounds;
146 const marg = <go.Margin>elem.margin;
147 const gw = marg.left + mb.width + marg.right;
148 const gh = marg.top + mb.height + marg.bottom;
149 if (y + gh > wrapy && i > 0) {
150 this._lineBreadths.push(colw);
151 this._lineLengths.push(y - pad.top);
152 x += colw + this.spacing.width;
153 if (x + gw <= wrapx) {
154 y = ystart + gh + this.spacing.height;
155 colw = gw;
156 } else {
157 y = ystart;
158 colw = 0;
159 break;
160 }
161 } else {
162 y += gh + this.spacing.height;
163 colw = Math.max(colw, gw);
164 }
165 maxy = Math.max(maxy, y);
166 }
167 this._lineBreadths.push(colw);
168 this._lineLengths.push(y - pad.top);
169 union.width = Math.max(0, x + colw - pad.left);
170 union.height = Math.max(0, Math.min(maxy, wrapy) - pad.top);
171 }
172 }
173
174 private isStretched(horiz: boolean, elt: go.GraphObject, panel: go.Panel): boolean {
175 let s = elt.stretch;
176 if (s === go.GraphObject.Default) s = panel.defaultStretch;
177 if (s === go.GraphObject.Fill) return true;
178 return s === (horiz ? go.GraphObject.Vertical : go.GraphObject.Horizontal);
179 }
180
181 private align(elt: go.GraphObject, panel: go.Panel): go.Spot {
182 let a = elt.alignment;
183 if (a.isDefault()) a = panel.defaultAlignment;
184 if (!a.isSpot()) a = go.Spot.Center;
185 return a;
186 }
187
188 public arrange(panel: go.Panel, elements: Array<go.GraphObject>, union: go.Rect): void {
189 const pad = <go.Margin>panel.padding;
190 let x = (this.direction === 180) ? union.width - pad.right : pad.left;
191 let y = (this.direction === 270) ? union.height - pad.bottom : pad.top;
192 const xstart = x;
193 const ystart = y;
194 if (this.direction === 0) {
195 let row = 0;
196 for (let i = 0; i < elements.length; i++) {
197 const elem = elements[i];
198 if (!elem.visible) continue;
199 const mb = elem.measuredBounds;
200 const marg = <go.Margin>elem.margin;
201 const gw = marg.left + mb.width + marg.right;
202 // use computed row length to decide whether to wrap, account for error accumulation
203 if (x - pad.left > this._lineLengths[row] - 0.00000005 && i > 0) {
204 y += this._lineBreadths[row++] + this.spacing.height;
205 x = xstart;
206 if (row === this._lineLengths.length) row--; // if on last row, stay there
207 }
208 const lastbr = this._lineBreadths[row]; // if row was clipped,
209 let h = (lastbr > 0) ? lastbr - marg.top - marg.bottom : 0; // use zero height
210 let ya = (lastbr > 0) ? y + marg.top : y; // and stay at same Y point
211 if ((lastbr > 0) && !this.isStretched(true, elem, panel)) { // if aligning...
212 const align = this.align(elem, panel); // compute alignment Spot
213 ya += align.y * (h - mb.height) + align.offsetY;
214 h = mb.height; // only considering Y axis
215 }
216 const xa = x + ((lastbr > 0) ? marg.left : 0);
217 this.arrangeElement(elem, xa, ya, mb.width, h);
218 x += gw + this.spacing.width;
219 }
220 } else if (this.direction === 180) {
221 let row = 0;
222 for (let i = 0; i < elements.length; i++) {
223 const elem = elements[i];
224 if (!elem.visible) continue;
225 const mb = elem.measuredBounds;
226 const marg = <go.Margin>elem.margin;
227 const gw = marg.left + mb.width + marg.right;
228 if (x - gw - pad.left < 0.00000005 && i > 0) {
229 y += this._lineBreadths[row++] + this.spacing.height;
230 x = xstart;
231 if (row === this._lineLengths.length) row--;
232 }
233 const lastbr = this._lineBreadths[row];
234 let h = (lastbr > 0) ? lastbr - marg.top - marg.bottom : 0;
235 let ya = (lastbr > 0) ? y + marg.top : y;
236 if ((lastbr > 0) && !this.isStretched(true, elem, panel)) {
237 const align = this.align(elem, panel);
238 ya += align.y * (h - mb.height) + align.offsetY;
239 h = mb.height;
240 }
241 const xa = x - gw + ((lastbr > 0) ? marg.left : 0);
242 this.arrangeElement(elem, xa, ya, mb.width, h);
243 x -= gw + this.spacing.width;
244 }
245 } else if (this.direction === 90) {
246 let col = 0;
247 for (let i = 0; i < elements.length; i++) {
248 const elem = elements[i];
249 if (!elem.visible) continue;
250 const mb = elem.measuredBounds;
251 const marg = <go.Margin>elem.margin;
252 const gh = marg.top + mb.height + marg.bottom;
253 if (y - pad.top > this._lineLengths[col] - 0.00000005 && i > 0) {
254 x += this._lineBreadths[col++] + this.spacing.width;
255 y = ystart;
256 if (col === this._lineLengths.length) col--;
257 }
258 const lastbr = this._lineBreadths[col];
259 let w = (lastbr > 0) ? lastbr - marg.left - marg.right : 0;
260 let xa = (lastbr > 0) ? x + marg.left : x;
261 if ((lastbr > 0) && !this.isStretched(false, elem, panel)) {
262 const align = this.align(elem, panel);
263 xa += align.x * (w - mb.width) + align.offsetX;
264 w = mb.width;
265 }
266 const ya = y + ((lastbr > 0) ? marg.top : 0);
267 this.arrangeElement(elem, xa, ya, w, mb.height);
268 y += gh + this.spacing.height;
269 }
270 } else if (this.direction === 270) {
271 let col = 0;
272 for (let i = 0; i < elements.length; i++) {
273 const elem = elements[i];
274 if (!elem.visible) continue;
275 const mb = elem.measuredBounds;
276 const marg = <go.Margin>elem.margin;
277 const gh = marg.top + mb.height + marg.bottom;
278 if (y - gh - pad.top < 0.00000005 && i > 0) {
279 x += this._lineBreadths[col++] + this.spacing.width;
280 y = ystart - gh;
281 if (col === this._lineLengths.length) col--;
282 } else {
283 y -= gh;
284 }
285 const lastbr = this._lineBreadths[col];
286 let w = (lastbr > 0) ? lastbr - marg.left - marg.right : 0;
287 let xa = (lastbr > 0) ? x + marg.left : x;
288 if ((lastbr > 0) && !this.isStretched(false, elem, panel)) {
289 const align = this.align(elem, panel);
290 xa += align.x * (w - mb.width) + align.offsetX;
291 w = mb.width;
292 }
293 const ya = y + ((lastbr > 0) ? marg.top : 0);
294 this.arrangeElement(elem, xa, ya, w, mb.height);
295 y -= this.spacing.height;
296 }
297 }
298 }
299
300 private static _ = (() => {
301 go.Panel.definePanelLayout('Flow', new PanelLayoutFlow());
302 })();
303}
304
\No newline at end of file