@hpcc-js/common 3.6.3 → 3.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +43 -43
- package/README.md +59 -59
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +1 -1
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +4 -4
- package/src/CanvasWidget.ts +31 -31
- package/src/Class.ts +72 -72
- package/src/Database.ts +860 -860
- package/src/Entity.ts +235 -235
- package/src/EntityCard.ts +66 -66
- package/src/EntityPin.ts +103 -103
- package/src/EntityRect.css +15 -15
- package/src/EntityRect.ts +254 -254
- package/src/EntityVertex.ts +86 -86
- package/src/FAChar.css +2 -2
- package/src/FAChar.ts +89 -89
- package/src/HTMLWidget.ts +191 -191
- package/src/IList.ts +4 -4
- package/src/IMenu.ts +5 -5
- package/src/Icon.css +9 -9
- package/src/Icon.ts +176 -176
- package/src/Image.ts +104 -104
- package/src/List.css +13 -13
- package/src/List.ts +102 -102
- package/src/Menu.css +23 -23
- package/src/Menu.ts +139 -139
- package/src/Palette.ts +341 -341
- package/src/Platform.ts +125 -125
- package/src/ProgressBar.ts +115 -115
- package/src/PropertyExt.ts +770 -770
- package/src/ResizeSurface.css +39 -39
- package/src/ResizeSurface.ts +225 -225
- package/src/SVGWidget.ts +583 -583
- package/src/SVGZoomWidget.css +12 -12
- package/src/SVGZoomWidget.ts +427 -427
- package/src/Shape.css +3 -3
- package/src/Shape.ts +186 -186
- package/src/Surface.css +35 -35
- package/src/Surface.ts +364 -364
- package/src/Text.css +4 -4
- package/src/Text.ts +131 -131
- package/src/TextBox.css +4 -4
- package/src/TextBox.ts +183 -183
- package/src/TitleBar.css +114 -114
- package/src/TitleBar.ts +407 -407
- package/src/Transition.ts +45 -45
- package/src/Utility.ts +839 -839
- package/src/Widget.css +8 -8
- package/src/Widget.ts +731 -731
- package/src/WidgetArray.ts +15 -15
- package/src/__package__.ts +3 -3
- package/src/index.ts +55 -55
package/src/Widget.ts
CHANGED
|
@@ -1,731 +1,731 @@
|
|
|
1
|
-
import { select as d3Select } from "d3-selection";
|
|
2
|
-
import "d3-transition";
|
|
3
|
-
import { Field, Grid } from "./Database.ts";
|
|
4
|
-
import { } from "./Platform.ts";
|
|
5
|
-
import { PropertyExt } from "./PropertyExt.ts";
|
|
6
|
-
import { debounce, textRect, TextRect, textSize, TextSize } from "./Utility.ts";
|
|
7
|
-
|
|
8
|
-
import "../src/Widget.css";
|
|
9
|
-
|
|
10
|
-
export { Field };
|
|
11
|
-
|
|
12
|
-
export type IPrimative = boolean | number | string | object;
|
|
13
|
-
export type IFieldType = "boolean" | "number" | "string" | "dataset" | "object" | "any";
|
|
14
|
-
export interface InputField {
|
|
15
|
-
id: string;
|
|
16
|
-
type: IFieldType;
|
|
17
|
-
multi?: boolean;
|
|
18
|
-
default?: IPrimative | InputField[];
|
|
19
|
-
children?: InputField[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface IPos {
|
|
23
|
-
x: number;
|
|
24
|
-
y: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ISize {
|
|
28
|
-
width: number;
|
|
29
|
-
height: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface BBox {
|
|
33
|
-
x: number;
|
|
34
|
-
y: number;
|
|
35
|
-
width: number;
|
|
36
|
-
height: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface DataMetaT {
|
|
40
|
-
min?: number;
|
|
41
|
-
max?: number;
|
|
42
|
-
mean?: number;
|
|
43
|
-
stdDev?: number;
|
|
44
|
-
sum?: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let widgetID = 0;
|
|
48
|
-
export abstract class Widget extends PropertyExt {
|
|
49
|
-
static _idSeed: string;
|
|
50
|
-
|
|
51
|
-
protected _tag: string;
|
|
52
|
-
protected _isRootNode: boolean = true;
|
|
53
|
-
|
|
54
|
-
protected _db = new Grid();
|
|
55
|
-
protected _pos;
|
|
56
|
-
protected _prevPos;
|
|
57
|
-
protected _size;
|
|
58
|
-
protected _widgetScale;
|
|
59
|
-
protected _visible;
|
|
60
|
-
protected _display;
|
|
61
|
-
protected _dataMeta: DataMetaT = {};
|
|
62
|
-
|
|
63
|
-
protected _target: null | HTMLElement | SVGElement;
|
|
64
|
-
protected _placeholderElement;
|
|
65
|
-
protected _parentWidget;
|
|
66
|
-
|
|
67
|
-
protected _element;
|
|
68
|
-
|
|
69
|
-
protected _renderCount;
|
|
70
|
-
|
|
71
|
-
protected _overlayElement;
|
|
72
|
-
|
|
73
|
-
constructor() {
|
|
74
|
-
super();
|
|
75
|
-
this._id = Widget._idSeed + widgetID++;
|
|
76
|
-
|
|
77
|
-
this._db = new Grid();
|
|
78
|
-
this._pos = { x: 0, y: 0 };
|
|
79
|
-
this._size = { width: 0, height: 0 };
|
|
80
|
-
this._widgetScale = 1;
|
|
81
|
-
this._visible = true;
|
|
82
|
-
|
|
83
|
-
this._target = null;
|
|
84
|
-
this._placeholderElement = null;
|
|
85
|
-
this._parentWidget = null;
|
|
86
|
-
|
|
87
|
-
this._element = d3Select(null);
|
|
88
|
-
|
|
89
|
-
this._renderCount = 0;
|
|
90
|
-
|
|
91
|
-
if ((window as any).__hpcc_debug) {
|
|
92
|
-
if ((window as any).g_all === undefined) {
|
|
93
|
-
(window as any).g_all = {};
|
|
94
|
-
}
|
|
95
|
-
(window as any).g_all[this._id] = this;
|
|
96
|
-
}
|
|
97
|
-
if ((window as any).__hpcc_theme) {
|
|
98
|
-
this.applyTheme((window as any).__hpcc_theme);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
columnChecksum() {
|
|
103
|
-
return this._db.fieldsChecksum();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
dataChecksum() {
|
|
107
|
-
return this._db.dataChecksum();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
importJSON(_: string | object): this {
|
|
111
|
-
this._db.json(_);
|
|
112
|
-
return this;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
importCSV(_: string): this {
|
|
116
|
-
this._db.csv(_);
|
|
117
|
-
return this;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
importTSV(_: string): this {
|
|
121
|
-
this._db.tsv(_);
|
|
122
|
-
return this;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export(_: "JSON" | "CSV" | "TSV" = "JSON") {
|
|
126
|
-
switch (_) {
|
|
127
|
-
case "CSV":
|
|
128
|
-
return this._db.csv();
|
|
129
|
-
case "TSV":
|
|
130
|
-
return this._db.tsv();
|
|
131
|
-
case "JSON":
|
|
132
|
-
default:
|
|
133
|
-
return this._db.json();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
leakCheck(newNode) {
|
|
138
|
-
const context = this;
|
|
139
|
-
const watchArray = [newNode];
|
|
140
|
-
const destructObserver = new MutationObserver(function (mutations) {
|
|
141
|
-
let leaks = false;
|
|
142
|
-
mutations.forEach(function (mutation) {
|
|
143
|
-
for (let i = 0; i < mutation.removedNodes.length; ++i) {
|
|
144
|
-
const node = mutation.removedNodes.item(i);
|
|
145
|
-
if (watchArray.indexOf(node) >= 0 && context._target) {
|
|
146
|
-
leaks = true;
|
|
147
|
-
destructObserver.disconnect();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
if (leaks) {
|
|
152
|
-
console.error("leak: " + context.id() + " - " + context.classID() + "\t\twidget.target(null); was not called for this widget before it was removed from the page.");
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
let pNode = newNode.parentNode;
|
|
156
|
-
while (pNode) {
|
|
157
|
-
destructObserver.observe(pNode, { childList: true });
|
|
158
|
-
watchArray.push(pNode);
|
|
159
|
-
pNode = pNode.parentNode;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
renderCount(): number {
|
|
164
|
-
return this._renderCount;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Implementation ---
|
|
168
|
-
columns(): string[];
|
|
169
|
-
columns(_: string[], asDefault?: boolean): this;
|
|
170
|
-
columns(_?: string[], asDefault?: boolean): string[] | this {
|
|
171
|
-
if (!arguments.length) return this._db.legacyColumns();
|
|
172
|
-
this._db.legacyColumns(_, asDefault);
|
|
173
|
-
return this;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
protected columnIdx(column: string): number {
|
|
177
|
-
return this.columns().indexOf(column);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
protected cellIdxFunc<T>(colIdx: number, defValue?: T): (row: any) => T {
|
|
181
|
-
return colIdx < 0 ? () => defValue : row => row[colIdx];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
protected cellFunc<T>(column: string, defValue?: T): (row: any) => T {
|
|
185
|
-
return this.cellIdxFunc<T>(this.columnIdx(column), defValue);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
parsedData() {
|
|
189
|
-
return this._db.parsedData();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
formattedData() {
|
|
193
|
-
return this._db.formattedData();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
data(): any;
|
|
197
|
-
data(_: any): this;
|
|
198
|
-
data(_?: any): any | this {
|
|
199
|
-
if (!arguments.length) return this._db.legacyData();
|
|
200
|
-
this._db.legacyData(_);
|
|
201
|
-
return this;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
cloneData() {
|
|
205
|
-
return this.data().map(function (row) { return row.slice(0); });
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
flattenData(columns: string[] = this.columns(), data: any = this.data()) {
|
|
209
|
-
const retVal = [];
|
|
210
|
-
data.forEach(function (row, rowIdx) {
|
|
211
|
-
columns.filter(function (_col, idx) { return idx > 0; }).forEach(function (_col, idx) {
|
|
212
|
-
const val = row[idx + 1];
|
|
213
|
-
if (typeof val !== "undefined") {
|
|
214
|
-
const newItem = {
|
|
215
|
-
rowIdx,
|
|
216
|
-
colIdx: idx + 1,
|
|
217
|
-
label: row[0],
|
|
218
|
-
value: val
|
|
219
|
-
};
|
|
220
|
-
retVal.push(newItem);
|
|
221
|
-
}
|
|
222
|
-
}, this);
|
|
223
|
-
}, this);
|
|
224
|
-
return retVal;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
rowToObj(row: any[]): object {
|
|
228
|
-
if (!row) return {};
|
|
229
|
-
const retVal: any = {};
|
|
230
|
-
this.fields().forEach(function (field, idx) {
|
|
231
|
-
retVal[field.label_default() || field.label()] = row[idx];
|
|
232
|
-
});
|
|
233
|
-
if (row.length === this.columns().length + 1) {
|
|
234
|
-
retVal.__lparam = row[this.columns().length];
|
|
235
|
-
}
|
|
236
|
-
return retVal;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
pos(): IPos;
|
|
240
|
-
pos(_: IPos): this;
|
|
241
|
-
pos(_?: IPos): IPos | this {
|
|
242
|
-
if (!arguments.length) return this._pos;
|
|
243
|
-
this._pos = _;
|
|
244
|
-
if (this._overlayElement) {
|
|
245
|
-
this._overlayElement
|
|
246
|
-
.attr("transform", "translate(" + _.x + "," + _.y + ")scale(" + this._widgetScale + ")")
|
|
247
|
-
;
|
|
248
|
-
}
|
|
249
|
-
return this;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
x(): number;
|
|
253
|
-
x(_): this;
|
|
254
|
-
x(_?): number | this {
|
|
255
|
-
if (!arguments.length) return this._pos.x;
|
|
256
|
-
this.pos({ x: _, y: this._pos.y });
|
|
257
|
-
return this;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
y(): number;
|
|
261
|
-
y(_): this;
|
|
262
|
-
y(_?): number | this {
|
|
263
|
-
if (!arguments.length) return this._pos.y;
|
|
264
|
-
this.pos({ x: this._pos.x, y: _ });
|
|
265
|
-
return this;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
size(): ISize;
|
|
269
|
-
size(_): this;
|
|
270
|
-
size(_?): ISize | this {
|
|
271
|
-
if (!arguments.length) return this._size;
|
|
272
|
-
this._size = _;
|
|
273
|
-
if (this._overlayElement) {
|
|
274
|
-
this._overlayElement
|
|
275
|
-
.attr("width", _.width)
|
|
276
|
-
.attr("height", _.height)
|
|
277
|
-
;
|
|
278
|
-
}
|
|
279
|
-
return this;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
width(): number;
|
|
283
|
-
width(_): this;
|
|
284
|
-
width(_?): number | this {
|
|
285
|
-
if (!arguments.length) return this._size.width;
|
|
286
|
-
this.size({ width: _, height: this._size.height });
|
|
287
|
-
return this;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
height(): number;
|
|
291
|
-
height(_): this;
|
|
292
|
-
height(_?): number | this {
|
|
293
|
-
if (!arguments.length) return this._size.height;
|
|
294
|
-
this.size({ width: this._size.width, height: _ });
|
|
295
|
-
return this;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
resize(size?: ISize, delta: ISize = { width: 0, height: 0 }) {
|
|
299
|
-
let width;
|
|
300
|
-
let height;
|
|
301
|
-
if (size && size.width && size.height) {
|
|
302
|
-
width = size.width;
|
|
303
|
-
height = size.height;
|
|
304
|
-
} else {
|
|
305
|
-
const style = window.getComputedStyle(this._target, null);
|
|
306
|
-
width = parseFloat(style.getPropertyValue("width")) - delta.width;
|
|
307
|
-
height = parseFloat(style.getPropertyValue("height")) - delta.height;
|
|
308
|
-
}
|
|
309
|
-
this.size({
|
|
310
|
-
width,
|
|
311
|
-
height
|
|
312
|
-
});
|
|
313
|
-
return this;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
scale(): number;
|
|
317
|
-
scale(_): this;
|
|
318
|
-
scale(_?): number | this {
|
|
319
|
-
if (!arguments.length) return this._widgetScale;
|
|
320
|
-
this._widgetScale = _;
|
|
321
|
-
if (this._overlayElement) {
|
|
322
|
-
this._overlayElement
|
|
323
|
-
.attr("transform", "translate(" + _.x + "," + _.y + ")scale(" + this._widgetScale + ")")
|
|
324
|
-
;
|
|
325
|
-
}
|
|
326
|
-
return this;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
visible(): boolean;
|
|
330
|
-
visible(_): this;
|
|
331
|
-
visible(_?): boolean | this {
|
|
332
|
-
if (!arguments.length) return this._visible;
|
|
333
|
-
this._visible = _;
|
|
334
|
-
if (this._element) {
|
|
335
|
-
this._element
|
|
336
|
-
.style("visibility", this._visible ? null : "hidden")
|
|
337
|
-
.style("opacity", this._visible ? null : 0)
|
|
338
|
-
;
|
|
339
|
-
}
|
|
340
|
-
return this;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
display(): boolean;
|
|
344
|
-
display(_): this;
|
|
345
|
-
display(_?): boolean | this {
|
|
346
|
-
if (!arguments.length) return this._display;
|
|
347
|
-
this._display = _;
|
|
348
|
-
if (this._element) {
|
|
349
|
-
this._element.style("display", this._display ? null : "none");
|
|
350
|
-
}
|
|
351
|
-
return this;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
dataMeta(): DataMetaT;
|
|
355
|
-
dataMeta(_): this;
|
|
356
|
-
dataMeta(_?): DataMetaT | this {
|
|
357
|
-
if (!arguments.length) return this._dataMeta;
|
|
358
|
-
this._dataMeta = _;
|
|
359
|
-
return this;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
private _appData = new Object({});
|
|
363
|
-
appData(key: string): any;
|
|
364
|
-
appData(key: string, value: any): this;
|
|
365
|
-
appData(key: string, value?: any): any | this {
|
|
366
|
-
if (arguments.length < 2) return this._appData[key];
|
|
367
|
-
this._appData[key] = value;
|
|
368
|
-
return this;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
calcSnap(snapSize) {
|
|
372
|
-
function snap(x, gridSize) {
|
|
373
|
-
function snapDelta(x2, gridSize2) {
|
|
374
|
-
let dx = x2 % gridSize2;
|
|
375
|
-
if (Math.abs(dx) > gridSize2 - Math.abs(dx)) {
|
|
376
|
-
dx = (gridSize2 - Math.abs(dx)) * (dx < 0 ? 1 : -1);
|
|
377
|
-
}
|
|
378
|
-
return dx;
|
|
379
|
-
}
|
|
380
|
-
return x - snapDelta(x, gridSize);
|
|
381
|
-
}
|
|
382
|
-
const l = snap(this._pos.x - this._size.width / 2, snapSize);
|
|
383
|
-
const t = snap(this._pos.y - this._size.height / 2, snapSize);
|
|
384
|
-
const r = snap(this._pos.x + this._size.width / 2, snapSize);
|
|
385
|
-
const b = snap(this._pos.y + this._size.height / 2, snapSize);
|
|
386
|
-
const w = r - l;
|
|
387
|
-
const h = b - t;
|
|
388
|
-
return [{ x: l + w / 2, y: t + h / 2 }, { width: w, height: h }];
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// DOM/SVG Node Helpers ---
|
|
392
|
-
toWidget(domNode): Widget | null {
|
|
393
|
-
if (!domNode) {
|
|
394
|
-
return null;
|
|
395
|
-
}
|
|
396
|
-
const element = d3Select(domNode);
|
|
397
|
-
if (element) {
|
|
398
|
-
const widget = element.datum();
|
|
399
|
-
if (widget && widget instanceof Widget) {
|
|
400
|
-
return widget;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
parentOverlay() {
|
|
407
|
-
return null;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
locateParentWidget(domNode?): Widget | null {
|
|
411
|
-
domNode = domNode || (this._target ? this._target.parentNode : null);
|
|
412
|
-
if (domNode) {
|
|
413
|
-
const widget = this.toWidget(domNode);
|
|
414
|
-
if (widget) {
|
|
415
|
-
return widget;
|
|
416
|
-
} else if (domNode.parentNode) {
|
|
417
|
-
return this.locateParentWidget(domNode.parentNode);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return null;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
locateSVGNode(domNode): SVGSVGElement | null {
|
|
424
|
-
if (!domNode) {
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
427
|
-
if (domNode.tagName === "svg") {
|
|
428
|
-
return domNode;
|
|
429
|
-
}
|
|
430
|
-
return this.locateSVGNode(domNode.parentNode);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
locateOverlayNode() {
|
|
434
|
-
let widget = this.locateParentWidget(this._target);
|
|
435
|
-
while (widget) {
|
|
436
|
-
const retVal = widget.parentOverlay();
|
|
437
|
-
if (retVal) {
|
|
438
|
-
return retVal;
|
|
439
|
-
}
|
|
440
|
-
widget = this.locateParentWidget(widget._target.parentNode);
|
|
441
|
-
}
|
|
442
|
-
return null;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
locateAncestor(classID): Widget | null {
|
|
446
|
-
return this.locateClosestAncestor([classID]);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
locateClosestAncestor(classIDArr): Widget | null {
|
|
450
|
-
let widget = this.locateParentWidget(this._target);
|
|
451
|
-
while (widget) {
|
|
452
|
-
if (classIDArr.indexOf(widget.classID()) !== -1) {
|
|
453
|
-
return widget;
|
|
454
|
-
}
|
|
455
|
-
widget = this.locateParentWidget(widget._target.parentNode);
|
|
456
|
-
}
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
getAbsolutePos(domNode, w, h) {
|
|
461
|
-
const root = this.locateSVGNode(domNode);
|
|
462
|
-
if (!root) {
|
|
463
|
-
return null;
|
|
464
|
-
}
|
|
465
|
-
let pos = root.createSVGPoint();
|
|
466
|
-
const ctm = domNode.getCTM();
|
|
467
|
-
pos = pos.matrixTransform(ctm);
|
|
468
|
-
const retVal: any = {
|
|
469
|
-
x: pos.x,
|
|
470
|
-
y: pos.y
|
|
471
|
-
};
|
|
472
|
-
if (w !== undefined && h !== undefined) {
|
|
473
|
-
let size = root.createSVGPoint();
|
|
474
|
-
size.x = w;
|
|
475
|
-
size.y = h;
|
|
476
|
-
size = size.matrixTransform(ctm);
|
|
477
|
-
retVal.width = size.x - pos.x;
|
|
478
|
-
retVal.height = size.y - pos.y;
|
|
479
|
-
}
|
|
480
|
-
return retVal;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
hasOverlay() {
|
|
484
|
-
return this._overlayElement;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
syncOverlay() {
|
|
488
|
-
if (this._size.width && this._size.height) {
|
|
489
|
-
const newPos = this.getAbsolutePos(this._overlayElement.node(), this._size.width, this._size.height);
|
|
490
|
-
if (newPos && (!this._prevPos || newPos.x !== this._prevPos.x || newPos.y !== this._prevPos.y || newPos.width !== this._prevPos.width || newPos.height !== this._prevPos.height)) {
|
|
491
|
-
const xScale = newPos.width / this._size.width;
|
|
492
|
-
const yScale = newPos.height / this._size.height;
|
|
493
|
-
this._placeholderElement
|
|
494
|
-
.style("left", newPos.x - (newPos.width / xScale) / 2 + "px")
|
|
495
|
-
.style("top", newPos.y - (newPos.height / yScale) / 2 + "px")
|
|
496
|
-
.style("width", newPos.width / xScale + "px")
|
|
497
|
-
.style("height", newPos.height / yScale + "px")
|
|
498
|
-
;
|
|
499
|
-
const transform = "scale(" + xScale + "," + yScale + ")";
|
|
500
|
-
this._placeholderElement
|
|
501
|
-
.style("transform", transform)
|
|
502
|
-
.style("-moz-transform", transform)
|
|
503
|
-
.style("-ms-transform", transform)
|
|
504
|
-
.style("-webkit-transform", transform)
|
|
505
|
-
.style("-o-transform", transform)
|
|
506
|
-
;
|
|
507
|
-
}
|
|
508
|
-
this._prevPos = newPos;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
getBBox(refresh = false, round = false): BBox {
|
|
513
|
-
return {
|
|
514
|
-
x: 0,
|
|
515
|
-
y: 0,
|
|
516
|
-
width: 0,
|
|
517
|
-
height: 0
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
textSize(_text: string | string[], fontName: string = "Verdana", fontSize: number = 12, bold: boolean = false): Readonly<TextSize> {
|
|
522
|
-
return textSize(_text, fontName, fontSize, bold);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
textRect(_text: string, fontName: string = "Verdana", fontSize: number = 12, bold: boolean = false): Readonly<TextRect> {
|
|
526
|
-
return textRect(_text, fontName, fontSize, bold);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
element() {
|
|
530
|
-
return this._element;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
node() {
|
|
534
|
-
return this._element.node();
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
target(): null | HTMLElement | SVGElement;
|
|
538
|
-
target(_: null | string | HTMLElement | SVGElement): this;
|
|
539
|
-
target(_?: null | string | HTMLElement | SVGElement): null | HTMLElement | SVGElement | this {
|
|
540
|
-
if (!arguments.length) return this._target;
|
|
541
|
-
if (this._target && _) {
|
|
542
|
-
throw new Error("Target can only be assigned once.");
|
|
543
|
-
}
|
|
544
|
-
if (_ === null) {
|
|
545
|
-
this._target = null;
|
|
546
|
-
if (this.renderCount()) {
|
|
547
|
-
this.exit();
|
|
548
|
-
}
|
|
549
|
-
} else if (typeof _ === "string") {
|
|
550
|
-
this._target = document.getElementById(_);
|
|
551
|
-
} else if (_ instanceof HTMLElement || _ instanceof SVGElement) {
|
|
552
|
-
this._target = _;
|
|
553
|
-
}
|
|
554
|
-
return this;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
isDOMHidden(): boolean {
|
|
558
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
|
|
559
|
-
// Note: Will return false for visible===hidden (which is ok as it still takes up space on the page)
|
|
560
|
-
return this._isRootNode && this._placeholderElement.node().offsetParent === null;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
hasSize() {
|
|
564
|
-
return !isNaN(this.width()) && !isNaN(this.height());
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
protected publishedWidgets(): Widget[] {
|
|
568
|
-
let widgets = [];
|
|
569
|
-
this.publishedProperties(true).forEach(function (meta) {
|
|
570
|
-
if (!meta.ext || meta.ext.render !== false) {
|
|
571
|
-
switch (meta.type) {
|
|
572
|
-
case "widget":
|
|
573
|
-
const widget = this[meta.id]();
|
|
574
|
-
if (widget) {
|
|
575
|
-
widgets.push(widget);
|
|
576
|
-
}
|
|
577
|
-
break;
|
|
578
|
-
case "widgetArray":
|
|
579
|
-
widgets = widgets.concat(this[meta.id]());
|
|
580
|
-
break;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}, this);
|
|
584
|
-
return widgets;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Render ---
|
|
588
|
-
private _prevNow = 0;
|
|
589
|
-
render(callback?: (w: Widget) => void) {
|
|
590
|
-
if ((window as any).__hpcc_debug) {
|
|
591
|
-
const now = Date.now();
|
|
592
|
-
if (now - this._prevNow < 500) {
|
|
593
|
-
console.error("Double Render: " + (now - this._prevNow) + " - " + this.id() + " - " + this.classID());
|
|
594
|
-
}
|
|
595
|
-
this._prevNow = now;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
callback = callback || function () { };
|
|
599
|
-
if (!this._placeholderElement || !this.visible() || this.isDOMHidden() || !this.hasSize()) {
|
|
600
|
-
callback(this);
|
|
601
|
-
return this;
|
|
602
|
-
}
|
|
603
|
-
if (this._placeholderElement) {
|
|
604
|
-
if (!this._tag)
|
|
605
|
-
throw new Error("No DOM tag specified");
|
|
606
|
-
|
|
607
|
-
const elements = this._placeholderElement.selectAll("#" + this._id).data([this], function (d) { return d._id; });
|
|
608
|
-
elements.enter().append(this._tag)
|
|
609
|
-
.classed(this._class, true)
|
|
610
|
-
.attr("id", this._id)
|
|
611
|
-
// .attr("opacity", 0.50) // Uncomment to debug position offsets ---
|
|
612
|
-
.each(function (context2) {
|
|
613
|
-
context2._element = d3Select(this);
|
|
614
|
-
context2.enter(this, context2._element);
|
|
615
|
-
if ((window as any).__hpcc_debug) {
|
|
616
|
-
context2.leakCheck(this);
|
|
617
|
-
}
|
|
618
|
-
})
|
|
619
|
-
.merge(elements)
|
|
620
|
-
.each(function (context2) {
|
|
621
|
-
const element = d3Select(this);
|
|
622
|
-
const classed = context2.classed();
|
|
623
|
-
for (const key in classed) {
|
|
624
|
-
element.classed(key, classed[key]);
|
|
625
|
-
}
|
|
626
|
-
context2.preUpdate(this, context2._element);
|
|
627
|
-
context2.update(this, context2._element);
|
|
628
|
-
context2.postUpdate(this, context2._element);
|
|
629
|
-
})
|
|
630
|
-
;
|
|
631
|
-
elements.exit()
|
|
632
|
-
.each(function (context2) {
|
|
633
|
-
d3Select(this).datum(null);
|
|
634
|
-
context2.exit(this, context2._element);
|
|
635
|
-
})
|
|
636
|
-
.remove()
|
|
637
|
-
;
|
|
638
|
-
this._renderCount++;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// ASync Render Contained Widgets ---
|
|
642
|
-
const widgets = this.publishedWidgets();
|
|
643
|
-
|
|
644
|
-
const context = this;
|
|
645
|
-
switch (widgets.length) {
|
|
646
|
-
case 0:
|
|
647
|
-
callback(this);
|
|
648
|
-
break;
|
|
649
|
-
case 1:
|
|
650
|
-
widgets[0].render(function () {
|
|
651
|
-
callback(context);
|
|
652
|
-
});
|
|
653
|
-
break;
|
|
654
|
-
default:
|
|
655
|
-
let renderCount = widgets.length;
|
|
656
|
-
widgets.forEach(function (widget, _idx) {
|
|
657
|
-
setTimeout(function () {
|
|
658
|
-
widget.render(function () {
|
|
659
|
-
if (--renderCount === 0) {
|
|
660
|
-
callback(context);
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
}, 0);
|
|
664
|
-
});
|
|
665
|
-
break;
|
|
666
|
-
}
|
|
667
|
-
return this;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
renderPromise(): Promise<Widget> {
|
|
671
|
-
return new Promise((resolve, reject) => {
|
|
672
|
-
this.render((w: Widget) => {
|
|
673
|
-
resolve(w);
|
|
674
|
-
});
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
private _lazyRender = debounce(function (debouncedCallback?: (w: Widget) => void) {
|
|
679
|
-
this.render(debouncedCallback);
|
|
680
|
-
}, 100);
|
|
681
|
-
|
|
682
|
-
lazyRender(callback?: (w: Widget) => void): this {
|
|
683
|
-
this._lazyRender(callback);
|
|
684
|
-
return this;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
animationFrameRender(): this {
|
|
688
|
-
if (requestAnimationFrame) {
|
|
689
|
-
requestAnimationFrame(() => {
|
|
690
|
-
this.render();
|
|
691
|
-
});
|
|
692
|
-
} else {
|
|
693
|
-
// Not a real replacement for requestAnimationFrame ---
|
|
694
|
-
this.renderPromise();
|
|
695
|
-
}
|
|
696
|
-
return this;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
enter(_domNode: HTMLElement, _element) { }
|
|
700
|
-
preUpdate(_domNode: HTMLElement, _element) { }
|
|
701
|
-
update(_domNode: HTMLElement, _element) { }
|
|
702
|
-
postUpdate(_domNode: HTMLElement, _element) { }
|
|
703
|
-
exit(_domNode?: HTMLElement, _element?) {
|
|
704
|
-
this.publishedWidgets().forEach(w => w.target(null));
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
Widget.prototype._class += " common_Widget";
|
|
708
|
-
|
|
709
|
-
export interface Widget {
|
|
710
|
-
fields(): Field[];
|
|
711
|
-
fields(_: Field[]): this;
|
|
712
|
-
classed(classID: string): boolean;
|
|
713
|
-
classed(classID: string, _: boolean): this;
|
|
714
|
-
classed(): { [classID: string]: boolean };
|
|
715
|
-
classed(_: { [classID: string]: boolean }): this;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
Widget._idSeed = "_w";
|
|
719
|
-
|
|
720
|
-
Widget.prototype.publishProxy("fields", "_db", "fields");
|
|
721
|
-
Widget.prototype.publish("classed", {}, "object", "HTML Classes", null, { tags: ["Private"] });
|
|
722
|
-
const origClassed = Widget.prototype.classed;
|
|
723
|
-
Widget.prototype.classed = function (this: Widget, str_obj?: string | { [classID: string]: boolean }, _?: boolean) {
|
|
724
|
-
if (typeof str_obj === "string") {
|
|
725
|
-
if (arguments.length === 1) return origClassed.call(this)[str_obj];
|
|
726
|
-
const classed = origClassed.call(this);
|
|
727
|
-
origClassed.call(this, { ...classed, [str_obj]: _ });
|
|
728
|
-
return this;
|
|
729
|
-
}
|
|
730
|
-
return origClassed.apply(this, arguments);
|
|
731
|
-
};
|
|
1
|
+
import { select as d3Select } from "d3-selection";
|
|
2
|
+
import "d3-transition";
|
|
3
|
+
import { Field, Grid } from "./Database.ts";
|
|
4
|
+
import { } from "./Platform.ts";
|
|
5
|
+
import { PropertyExt } from "./PropertyExt.ts";
|
|
6
|
+
import { debounce, textRect, TextRect, textSize, TextSize } from "./Utility.ts";
|
|
7
|
+
|
|
8
|
+
import "../src/Widget.css";
|
|
9
|
+
|
|
10
|
+
export { Field };
|
|
11
|
+
|
|
12
|
+
export type IPrimative = boolean | number | string | object;
|
|
13
|
+
export type IFieldType = "boolean" | "number" | "string" | "dataset" | "object" | "any";
|
|
14
|
+
export interface InputField {
|
|
15
|
+
id: string;
|
|
16
|
+
type: IFieldType;
|
|
17
|
+
multi?: boolean;
|
|
18
|
+
default?: IPrimative | InputField[];
|
|
19
|
+
children?: InputField[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IPos {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ISize {
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BBox {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DataMetaT {
|
|
40
|
+
min?: number;
|
|
41
|
+
max?: number;
|
|
42
|
+
mean?: number;
|
|
43
|
+
stdDev?: number;
|
|
44
|
+
sum?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let widgetID = 0;
|
|
48
|
+
export abstract class Widget extends PropertyExt {
|
|
49
|
+
static _idSeed: string;
|
|
50
|
+
|
|
51
|
+
protected _tag: string;
|
|
52
|
+
protected _isRootNode: boolean = true;
|
|
53
|
+
|
|
54
|
+
protected _db = new Grid();
|
|
55
|
+
protected _pos;
|
|
56
|
+
protected _prevPos;
|
|
57
|
+
protected _size;
|
|
58
|
+
protected _widgetScale;
|
|
59
|
+
protected _visible;
|
|
60
|
+
protected _display;
|
|
61
|
+
protected _dataMeta: DataMetaT = {};
|
|
62
|
+
|
|
63
|
+
protected _target: null | HTMLElement | SVGElement;
|
|
64
|
+
protected _placeholderElement;
|
|
65
|
+
protected _parentWidget;
|
|
66
|
+
|
|
67
|
+
protected _element;
|
|
68
|
+
|
|
69
|
+
protected _renderCount;
|
|
70
|
+
|
|
71
|
+
protected _overlayElement;
|
|
72
|
+
|
|
73
|
+
constructor() {
|
|
74
|
+
super();
|
|
75
|
+
this._id = Widget._idSeed + widgetID++;
|
|
76
|
+
|
|
77
|
+
this._db = new Grid();
|
|
78
|
+
this._pos = { x: 0, y: 0 };
|
|
79
|
+
this._size = { width: 0, height: 0 };
|
|
80
|
+
this._widgetScale = 1;
|
|
81
|
+
this._visible = true;
|
|
82
|
+
|
|
83
|
+
this._target = null;
|
|
84
|
+
this._placeholderElement = null;
|
|
85
|
+
this._parentWidget = null;
|
|
86
|
+
|
|
87
|
+
this._element = d3Select(null);
|
|
88
|
+
|
|
89
|
+
this._renderCount = 0;
|
|
90
|
+
|
|
91
|
+
if ((window as any).__hpcc_debug) {
|
|
92
|
+
if ((window as any).g_all === undefined) {
|
|
93
|
+
(window as any).g_all = {};
|
|
94
|
+
}
|
|
95
|
+
(window as any).g_all[this._id] = this;
|
|
96
|
+
}
|
|
97
|
+
if ((window as any).__hpcc_theme) {
|
|
98
|
+
this.applyTheme((window as any).__hpcc_theme);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
columnChecksum() {
|
|
103
|
+
return this._db.fieldsChecksum();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
dataChecksum() {
|
|
107
|
+
return this._db.dataChecksum();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
importJSON(_: string | object): this {
|
|
111
|
+
this._db.json(_);
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
importCSV(_: string): this {
|
|
116
|
+
this._db.csv(_);
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
importTSV(_: string): this {
|
|
121
|
+
this._db.tsv(_);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export(_: "JSON" | "CSV" | "TSV" = "JSON") {
|
|
126
|
+
switch (_) {
|
|
127
|
+
case "CSV":
|
|
128
|
+
return this._db.csv();
|
|
129
|
+
case "TSV":
|
|
130
|
+
return this._db.tsv();
|
|
131
|
+
case "JSON":
|
|
132
|
+
default:
|
|
133
|
+
return this._db.json();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
leakCheck(newNode) {
|
|
138
|
+
const context = this;
|
|
139
|
+
const watchArray = [newNode];
|
|
140
|
+
const destructObserver = new MutationObserver(function (mutations) {
|
|
141
|
+
let leaks = false;
|
|
142
|
+
mutations.forEach(function (mutation) {
|
|
143
|
+
for (let i = 0; i < mutation.removedNodes.length; ++i) {
|
|
144
|
+
const node = mutation.removedNodes.item(i);
|
|
145
|
+
if (watchArray.indexOf(node) >= 0 && context._target) {
|
|
146
|
+
leaks = true;
|
|
147
|
+
destructObserver.disconnect();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
if (leaks) {
|
|
152
|
+
console.error("leak: " + context.id() + " - " + context.classID() + "\t\twidget.target(null); was not called for this widget before it was removed from the page.");
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
let pNode = newNode.parentNode;
|
|
156
|
+
while (pNode) {
|
|
157
|
+
destructObserver.observe(pNode, { childList: true });
|
|
158
|
+
watchArray.push(pNode);
|
|
159
|
+
pNode = pNode.parentNode;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
renderCount(): number {
|
|
164
|
+
return this._renderCount;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Implementation ---
|
|
168
|
+
columns(): string[];
|
|
169
|
+
columns(_: string[], asDefault?: boolean): this;
|
|
170
|
+
columns(_?: string[], asDefault?: boolean): string[] | this {
|
|
171
|
+
if (!arguments.length) return this._db.legacyColumns();
|
|
172
|
+
this._db.legacyColumns(_, asDefault);
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected columnIdx(column: string): number {
|
|
177
|
+
return this.columns().indexOf(column);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected cellIdxFunc<T>(colIdx: number, defValue?: T): (row: any) => T {
|
|
181
|
+
return colIdx < 0 ? () => defValue : row => row[colIdx];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
protected cellFunc<T>(column: string, defValue?: T): (row: any) => T {
|
|
185
|
+
return this.cellIdxFunc<T>(this.columnIdx(column), defValue);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
parsedData() {
|
|
189
|
+
return this._db.parsedData();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
formattedData() {
|
|
193
|
+
return this._db.formattedData();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
data(): any;
|
|
197
|
+
data(_: any): this;
|
|
198
|
+
data(_?: any): any | this {
|
|
199
|
+
if (!arguments.length) return this._db.legacyData();
|
|
200
|
+
this._db.legacyData(_);
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
cloneData() {
|
|
205
|
+
return this.data().map(function (row) { return row.slice(0); });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
flattenData(columns: string[] = this.columns(), data: any = this.data()) {
|
|
209
|
+
const retVal = [];
|
|
210
|
+
data.forEach(function (row, rowIdx) {
|
|
211
|
+
columns.filter(function (_col, idx) { return idx > 0; }).forEach(function (_col, idx) {
|
|
212
|
+
const val = row[idx + 1];
|
|
213
|
+
if (typeof val !== "undefined") {
|
|
214
|
+
const newItem = {
|
|
215
|
+
rowIdx,
|
|
216
|
+
colIdx: idx + 1,
|
|
217
|
+
label: row[0],
|
|
218
|
+
value: val
|
|
219
|
+
};
|
|
220
|
+
retVal.push(newItem);
|
|
221
|
+
}
|
|
222
|
+
}, this);
|
|
223
|
+
}, this);
|
|
224
|
+
return retVal;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
rowToObj(row: any[]): object {
|
|
228
|
+
if (!row) return {};
|
|
229
|
+
const retVal: any = {};
|
|
230
|
+
this.fields().forEach(function (field, idx) {
|
|
231
|
+
retVal[field.label_default() || field.label()] = row[idx];
|
|
232
|
+
});
|
|
233
|
+
if (row.length === this.columns().length + 1) {
|
|
234
|
+
retVal.__lparam = row[this.columns().length];
|
|
235
|
+
}
|
|
236
|
+
return retVal;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
pos(): IPos;
|
|
240
|
+
pos(_: IPos): this;
|
|
241
|
+
pos(_?: IPos): IPos | this {
|
|
242
|
+
if (!arguments.length) return this._pos;
|
|
243
|
+
this._pos = _;
|
|
244
|
+
if (this._overlayElement) {
|
|
245
|
+
this._overlayElement
|
|
246
|
+
.attr("transform", "translate(" + _.x + "," + _.y + ")scale(" + this._widgetScale + ")")
|
|
247
|
+
;
|
|
248
|
+
}
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
x(): number;
|
|
253
|
+
x(_): this;
|
|
254
|
+
x(_?): number | this {
|
|
255
|
+
if (!arguments.length) return this._pos.x;
|
|
256
|
+
this.pos({ x: _, y: this._pos.y });
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
y(): number;
|
|
261
|
+
y(_): this;
|
|
262
|
+
y(_?): number | this {
|
|
263
|
+
if (!arguments.length) return this._pos.y;
|
|
264
|
+
this.pos({ x: this._pos.x, y: _ });
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
size(): ISize;
|
|
269
|
+
size(_): this;
|
|
270
|
+
size(_?): ISize | this {
|
|
271
|
+
if (!arguments.length) return this._size;
|
|
272
|
+
this._size = _;
|
|
273
|
+
if (this._overlayElement) {
|
|
274
|
+
this._overlayElement
|
|
275
|
+
.attr("width", _.width)
|
|
276
|
+
.attr("height", _.height)
|
|
277
|
+
;
|
|
278
|
+
}
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
width(): number;
|
|
283
|
+
width(_): this;
|
|
284
|
+
width(_?): number | this {
|
|
285
|
+
if (!arguments.length) return this._size.width;
|
|
286
|
+
this.size({ width: _, height: this._size.height });
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
height(): number;
|
|
291
|
+
height(_): this;
|
|
292
|
+
height(_?): number | this {
|
|
293
|
+
if (!arguments.length) return this._size.height;
|
|
294
|
+
this.size({ width: this._size.width, height: _ });
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
resize(size?: ISize, delta: ISize = { width: 0, height: 0 }) {
|
|
299
|
+
let width;
|
|
300
|
+
let height;
|
|
301
|
+
if (size && size.width && size.height) {
|
|
302
|
+
width = size.width;
|
|
303
|
+
height = size.height;
|
|
304
|
+
} else {
|
|
305
|
+
const style = window.getComputedStyle(this._target, null);
|
|
306
|
+
width = parseFloat(style.getPropertyValue("width")) - delta.width;
|
|
307
|
+
height = parseFloat(style.getPropertyValue("height")) - delta.height;
|
|
308
|
+
}
|
|
309
|
+
this.size({
|
|
310
|
+
width,
|
|
311
|
+
height
|
|
312
|
+
});
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
scale(): number;
|
|
317
|
+
scale(_): this;
|
|
318
|
+
scale(_?): number | this {
|
|
319
|
+
if (!arguments.length) return this._widgetScale;
|
|
320
|
+
this._widgetScale = _;
|
|
321
|
+
if (this._overlayElement) {
|
|
322
|
+
this._overlayElement
|
|
323
|
+
.attr("transform", "translate(" + _.x + "," + _.y + ")scale(" + this._widgetScale + ")")
|
|
324
|
+
;
|
|
325
|
+
}
|
|
326
|
+
return this;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
visible(): boolean;
|
|
330
|
+
visible(_): this;
|
|
331
|
+
visible(_?): boolean | this {
|
|
332
|
+
if (!arguments.length) return this._visible;
|
|
333
|
+
this._visible = _;
|
|
334
|
+
if (this._element) {
|
|
335
|
+
this._element
|
|
336
|
+
.style("visibility", this._visible ? null : "hidden")
|
|
337
|
+
.style("opacity", this._visible ? null : 0)
|
|
338
|
+
;
|
|
339
|
+
}
|
|
340
|
+
return this;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
display(): boolean;
|
|
344
|
+
display(_): this;
|
|
345
|
+
display(_?): boolean | this {
|
|
346
|
+
if (!arguments.length) return this._display;
|
|
347
|
+
this._display = _;
|
|
348
|
+
if (this._element) {
|
|
349
|
+
this._element.style("display", this._display ? null : "none");
|
|
350
|
+
}
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
dataMeta(): DataMetaT;
|
|
355
|
+
dataMeta(_): this;
|
|
356
|
+
dataMeta(_?): DataMetaT | this {
|
|
357
|
+
if (!arguments.length) return this._dataMeta;
|
|
358
|
+
this._dataMeta = _;
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private _appData = new Object({});
|
|
363
|
+
appData(key: string): any;
|
|
364
|
+
appData(key: string, value: any): this;
|
|
365
|
+
appData(key: string, value?: any): any | this {
|
|
366
|
+
if (arguments.length < 2) return this._appData[key];
|
|
367
|
+
this._appData[key] = value;
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
calcSnap(snapSize) {
|
|
372
|
+
function snap(x, gridSize) {
|
|
373
|
+
function snapDelta(x2, gridSize2) {
|
|
374
|
+
let dx = x2 % gridSize2;
|
|
375
|
+
if (Math.abs(dx) > gridSize2 - Math.abs(dx)) {
|
|
376
|
+
dx = (gridSize2 - Math.abs(dx)) * (dx < 0 ? 1 : -1);
|
|
377
|
+
}
|
|
378
|
+
return dx;
|
|
379
|
+
}
|
|
380
|
+
return x - snapDelta(x, gridSize);
|
|
381
|
+
}
|
|
382
|
+
const l = snap(this._pos.x - this._size.width / 2, snapSize);
|
|
383
|
+
const t = snap(this._pos.y - this._size.height / 2, snapSize);
|
|
384
|
+
const r = snap(this._pos.x + this._size.width / 2, snapSize);
|
|
385
|
+
const b = snap(this._pos.y + this._size.height / 2, snapSize);
|
|
386
|
+
const w = r - l;
|
|
387
|
+
const h = b - t;
|
|
388
|
+
return [{ x: l + w / 2, y: t + h / 2 }, { width: w, height: h }];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// DOM/SVG Node Helpers ---
|
|
392
|
+
toWidget(domNode): Widget | null {
|
|
393
|
+
if (!domNode) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
const element = d3Select(domNode);
|
|
397
|
+
if (element) {
|
|
398
|
+
const widget = element.datum();
|
|
399
|
+
if (widget && widget instanceof Widget) {
|
|
400
|
+
return widget;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
parentOverlay() {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
locateParentWidget(domNode?): Widget | null {
|
|
411
|
+
domNode = domNode || (this._target ? this._target.parentNode : null);
|
|
412
|
+
if (domNode) {
|
|
413
|
+
const widget = this.toWidget(domNode);
|
|
414
|
+
if (widget) {
|
|
415
|
+
return widget;
|
|
416
|
+
} else if (domNode.parentNode) {
|
|
417
|
+
return this.locateParentWidget(domNode.parentNode);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
locateSVGNode(domNode): SVGSVGElement | null {
|
|
424
|
+
if (!domNode) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
if (domNode.tagName === "svg") {
|
|
428
|
+
return domNode;
|
|
429
|
+
}
|
|
430
|
+
return this.locateSVGNode(domNode.parentNode);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
locateOverlayNode() {
|
|
434
|
+
let widget = this.locateParentWidget(this._target);
|
|
435
|
+
while (widget) {
|
|
436
|
+
const retVal = widget.parentOverlay();
|
|
437
|
+
if (retVal) {
|
|
438
|
+
return retVal;
|
|
439
|
+
}
|
|
440
|
+
widget = this.locateParentWidget(widget._target.parentNode);
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
locateAncestor(classID): Widget | null {
|
|
446
|
+
return this.locateClosestAncestor([classID]);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
locateClosestAncestor(classIDArr): Widget | null {
|
|
450
|
+
let widget = this.locateParentWidget(this._target);
|
|
451
|
+
while (widget) {
|
|
452
|
+
if (classIDArr.indexOf(widget.classID()) !== -1) {
|
|
453
|
+
return widget;
|
|
454
|
+
}
|
|
455
|
+
widget = this.locateParentWidget(widget._target.parentNode);
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
getAbsolutePos(domNode, w, h) {
|
|
461
|
+
const root = this.locateSVGNode(domNode);
|
|
462
|
+
if (!root) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
let pos = root.createSVGPoint();
|
|
466
|
+
const ctm = domNode.getCTM();
|
|
467
|
+
pos = pos.matrixTransform(ctm);
|
|
468
|
+
const retVal: any = {
|
|
469
|
+
x: pos.x,
|
|
470
|
+
y: pos.y
|
|
471
|
+
};
|
|
472
|
+
if (w !== undefined && h !== undefined) {
|
|
473
|
+
let size = root.createSVGPoint();
|
|
474
|
+
size.x = w;
|
|
475
|
+
size.y = h;
|
|
476
|
+
size = size.matrixTransform(ctm);
|
|
477
|
+
retVal.width = size.x - pos.x;
|
|
478
|
+
retVal.height = size.y - pos.y;
|
|
479
|
+
}
|
|
480
|
+
return retVal;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
hasOverlay() {
|
|
484
|
+
return this._overlayElement;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
syncOverlay() {
|
|
488
|
+
if (this._size.width && this._size.height) {
|
|
489
|
+
const newPos = this.getAbsolutePos(this._overlayElement.node(), this._size.width, this._size.height);
|
|
490
|
+
if (newPos && (!this._prevPos || newPos.x !== this._prevPos.x || newPos.y !== this._prevPos.y || newPos.width !== this._prevPos.width || newPos.height !== this._prevPos.height)) {
|
|
491
|
+
const xScale = newPos.width / this._size.width;
|
|
492
|
+
const yScale = newPos.height / this._size.height;
|
|
493
|
+
this._placeholderElement
|
|
494
|
+
.style("left", newPos.x - (newPos.width / xScale) / 2 + "px")
|
|
495
|
+
.style("top", newPos.y - (newPos.height / yScale) / 2 + "px")
|
|
496
|
+
.style("width", newPos.width / xScale + "px")
|
|
497
|
+
.style("height", newPos.height / yScale + "px")
|
|
498
|
+
;
|
|
499
|
+
const transform = "scale(" + xScale + "," + yScale + ")";
|
|
500
|
+
this._placeholderElement
|
|
501
|
+
.style("transform", transform)
|
|
502
|
+
.style("-moz-transform", transform)
|
|
503
|
+
.style("-ms-transform", transform)
|
|
504
|
+
.style("-webkit-transform", transform)
|
|
505
|
+
.style("-o-transform", transform)
|
|
506
|
+
;
|
|
507
|
+
}
|
|
508
|
+
this._prevPos = newPos;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
getBBox(refresh = false, round = false): BBox {
|
|
513
|
+
return {
|
|
514
|
+
x: 0,
|
|
515
|
+
y: 0,
|
|
516
|
+
width: 0,
|
|
517
|
+
height: 0
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
textSize(_text: string | string[], fontName: string = "Verdana", fontSize: number = 12, bold: boolean = false): Readonly<TextSize> {
|
|
522
|
+
return textSize(_text, fontName, fontSize, bold);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
textRect(_text: string, fontName: string = "Verdana", fontSize: number = 12, bold: boolean = false): Readonly<TextRect> {
|
|
526
|
+
return textRect(_text, fontName, fontSize, bold);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
element() {
|
|
530
|
+
return this._element;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
node() {
|
|
534
|
+
return this._element.node();
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
target(): null | HTMLElement | SVGElement;
|
|
538
|
+
target(_: null | string | HTMLElement | SVGElement): this;
|
|
539
|
+
target(_?: null | string | HTMLElement | SVGElement): null | HTMLElement | SVGElement | this {
|
|
540
|
+
if (!arguments.length) return this._target;
|
|
541
|
+
if (this._target && _) {
|
|
542
|
+
throw new Error("Target can only be assigned once.");
|
|
543
|
+
}
|
|
544
|
+
if (_ === null) {
|
|
545
|
+
this._target = null;
|
|
546
|
+
if (this.renderCount()) {
|
|
547
|
+
this.exit();
|
|
548
|
+
}
|
|
549
|
+
} else if (typeof _ === "string") {
|
|
550
|
+
this._target = document.getElementById(_);
|
|
551
|
+
} else if (_ instanceof HTMLElement || _ instanceof SVGElement) {
|
|
552
|
+
this._target = _;
|
|
553
|
+
}
|
|
554
|
+
return this;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
isDOMHidden(): boolean {
|
|
558
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
|
|
559
|
+
// Note: Will return false for visible===hidden (which is ok as it still takes up space on the page)
|
|
560
|
+
return this._isRootNode && this._placeholderElement.node().offsetParent === null;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
hasSize() {
|
|
564
|
+
return !isNaN(this.width()) && !isNaN(this.height());
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
protected publishedWidgets(): Widget[] {
|
|
568
|
+
let widgets = [];
|
|
569
|
+
this.publishedProperties(true).forEach(function (meta) {
|
|
570
|
+
if (!meta.ext || meta.ext.render !== false) {
|
|
571
|
+
switch (meta.type) {
|
|
572
|
+
case "widget":
|
|
573
|
+
const widget = this[meta.id]();
|
|
574
|
+
if (widget) {
|
|
575
|
+
widgets.push(widget);
|
|
576
|
+
}
|
|
577
|
+
break;
|
|
578
|
+
case "widgetArray":
|
|
579
|
+
widgets = widgets.concat(this[meta.id]());
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}, this);
|
|
584
|
+
return widgets;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Render ---
|
|
588
|
+
private _prevNow = 0;
|
|
589
|
+
render(callback?: (w: Widget) => void) {
|
|
590
|
+
if ((window as any).__hpcc_debug) {
|
|
591
|
+
const now = Date.now();
|
|
592
|
+
if (now - this._prevNow < 500) {
|
|
593
|
+
console.error("Double Render: " + (now - this._prevNow) + " - " + this.id() + " - " + this.classID());
|
|
594
|
+
}
|
|
595
|
+
this._prevNow = now;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
callback = callback || function () { };
|
|
599
|
+
if (!this._placeholderElement || !this.visible() || this.isDOMHidden() || !this.hasSize()) {
|
|
600
|
+
callback(this);
|
|
601
|
+
return this;
|
|
602
|
+
}
|
|
603
|
+
if (this._placeholderElement) {
|
|
604
|
+
if (!this._tag)
|
|
605
|
+
throw new Error("No DOM tag specified");
|
|
606
|
+
|
|
607
|
+
const elements = this._placeholderElement.selectAll("#" + this._id).data([this], function (d) { return d._id; });
|
|
608
|
+
elements.enter().append(this._tag)
|
|
609
|
+
.classed(this._class, true)
|
|
610
|
+
.attr("id", this._id)
|
|
611
|
+
// .attr("opacity", 0.50) // Uncomment to debug position offsets ---
|
|
612
|
+
.each(function (context2) {
|
|
613
|
+
context2._element = d3Select(this);
|
|
614
|
+
context2.enter(this, context2._element);
|
|
615
|
+
if ((window as any).__hpcc_debug) {
|
|
616
|
+
context2.leakCheck(this);
|
|
617
|
+
}
|
|
618
|
+
})
|
|
619
|
+
.merge(elements)
|
|
620
|
+
.each(function (context2) {
|
|
621
|
+
const element = d3Select(this);
|
|
622
|
+
const classed = context2.classed();
|
|
623
|
+
for (const key in classed) {
|
|
624
|
+
element.classed(key, classed[key]);
|
|
625
|
+
}
|
|
626
|
+
context2.preUpdate(this, context2._element);
|
|
627
|
+
context2.update(this, context2._element);
|
|
628
|
+
context2.postUpdate(this, context2._element);
|
|
629
|
+
})
|
|
630
|
+
;
|
|
631
|
+
elements.exit()
|
|
632
|
+
.each(function (context2) {
|
|
633
|
+
d3Select(this).datum(null);
|
|
634
|
+
context2.exit(this, context2._element);
|
|
635
|
+
})
|
|
636
|
+
.remove()
|
|
637
|
+
;
|
|
638
|
+
this._renderCount++;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ASync Render Contained Widgets ---
|
|
642
|
+
const widgets = this.publishedWidgets();
|
|
643
|
+
|
|
644
|
+
const context = this;
|
|
645
|
+
switch (widgets.length) {
|
|
646
|
+
case 0:
|
|
647
|
+
callback(this);
|
|
648
|
+
break;
|
|
649
|
+
case 1:
|
|
650
|
+
widgets[0].render(function () {
|
|
651
|
+
callback(context);
|
|
652
|
+
});
|
|
653
|
+
break;
|
|
654
|
+
default:
|
|
655
|
+
let renderCount = widgets.length;
|
|
656
|
+
widgets.forEach(function (widget, _idx) {
|
|
657
|
+
setTimeout(function () {
|
|
658
|
+
widget.render(function () {
|
|
659
|
+
if (--renderCount === 0) {
|
|
660
|
+
callback(context);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
}, 0);
|
|
664
|
+
});
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
return this;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
renderPromise(): Promise<Widget> {
|
|
671
|
+
return new Promise((resolve, reject) => {
|
|
672
|
+
this.render((w: Widget) => {
|
|
673
|
+
resolve(w);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private _lazyRender = debounce(function (debouncedCallback?: (w: Widget) => void) {
|
|
679
|
+
this.render(debouncedCallback);
|
|
680
|
+
}, 100);
|
|
681
|
+
|
|
682
|
+
lazyRender(callback?: (w: Widget) => void): this {
|
|
683
|
+
this._lazyRender(callback);
|
|
684
|
+
return this;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
animationFrameRender(): this {
|
|
688
|
+
if (requestAnimationFrame) {
|
|
689
|
+
requestAnimationFrame(() => {
|
|
690
|
+
this.render();
|
|
691
|
+
});
|
|
692
|
+
} else {
|
|
693
|
+
// Not a real replacement for requestAnimationFrame ---
|
|
694
|
+
this.renderPromise();
|
|
695
|
+
}
|
|
696
|
+
return this;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
enter(_domNode: HTMLElement, _element) { }
|
|
700
|
+
preUpdate(_domNode: HTMLElement, _element) { }
|
|
701
|
+
update(_domNode: HTMLElement, _element) { }
|
|
702
|
+
postUpdate(_domNode: HTMLElement, _element) { }
|
|
703
|
+
exit(_domNode?: HTMLElement, _element?) {
|
|
704
|
+
this.publishedWidgets().forEach(w => w.target(null));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
Widget.prototype._class += " common_Widget";
|
|
708
|
+
|
|
709
|
+
export interface Widget {
|
|
710
|
+
fields(): Field[];
|
|
711
|
+
fields(_: Field[]): this;
|
|
712
|
+
classed(classID: string): boolean;
|
|
713
|
+
classed(classID: string, _: boolean): this;
|
|
714
|
+
classed(): { [classID: string]: boolean };
|
|
715
|
+
classed(_: { [classID: string]: boolean }): this;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
Widget._idSeed = "_w";
|
|
719
|
+
|
|
720
|
+
Widget.prototype.publishProxy("fields", "_db", "fields");
|
|
721
|
+
Widget.prototype.publish("classed", {}, "object", "HTML Classes", null, { tags: ["Private"] });
|
|
722
|
+
const origClassed = Widget.prototype.classed;
|
|
723
|
+
Widget.prototype.classed = function (this: Widget, str_obj?: string | { [classID: string]: boolean }, _?: boolean) {
|
|
724
|
+
if (typeof str_obj === "string") {
|
|
725
|
+
if (arguments.length === 1) return origClassed.call(this)[str_obj];
|
|
726
|
+
const classed = origClassed.call(this);
|
|
727
|
+
origClassed.call(this, { ...classed, [str_obj]: _ });
|
|
728
|
+
return this;
|
|
729
|
+
}
|
|
730
|
+
return origClassed.apply(this, arguments);
|
|
731
|
+
};
|