@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.
Files changed (54) hide show
  1. package/LICENSE +43 -43
  2. package/README.md +59 -59
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.umd.cjs +1 -1
  6. package/dist/index.umd.cjs.map +1 -1
  7. package/package.json +4 -4
  8. package/src/CanvasWidget.ts +31 -31
  9. package/src/Class.ts +72 -72
  10. package/src/Database.ts +860 -860
  11. package/src/Entity.ts +235 -235
  12. package/src/EntityCard.ts +66 -66
  13. package/src/EntityPin.ts +103 -103
  14. package/src/EntityRect.css +15 -15
  15. package/src/EntityRect.ts +254 -254
  16. package/src/EntityVertex.ts +86 -86
  17. package/src/FAChar.css +2 -2
  18. package/src/FAChar.ts +89 -89
  19. package/src/HTMLWidget.ts +191 -191
  20. package/src/IList.ts +4 -4
  21. package/src/IMenu.ts +5 -5
  22. package/src/Icon.css +9 -9
  23. package/src/Icon.ts +176 -176
  24. package/src/Image.ts +104 -104
  25. package/src/List.css +13 -13
  26. package/src/List.ts +102 -102
  27. package/src/Menu.css +23 -23
  28. package/src/Menu.ts +139 -139
  29. package/src/Palette.ts +341 -341
  30. package/src/Platform.ts +125 -125
  31. package/src/ProgressBar.ts +115 -115
  32. package/src/PropertyExt.ts +770 -770
  33. package/src/ResizeSurface.css +39 -39
  34. package/src/ResizeSurface.ts +225 -225
  35. package/src/SVGWidget.ts +583 -583
  36. package/src/SVGZoomWidget.css +12 -12
  37. package/src/SVGZoomWidget.ts +427 -427
  38. package/src/Shape.css +3 -3
  39. package/src/Shape.ts +186 -186
  40. package/src/Surface.css +35 -35
  41. package/src/Surface.ts +364 -364
  42. package/src/Text.css +4 -4
  43. package/src/Text.ts +131 -131
  44. package/src/TextBox.css +4 -4
  45. package/src/TextBox.ts +183 -183
  46. package/src/TitleBar.css +114 -114
  47. package/src/TitleBar.ts +407 -407
  48. package/src/Transition.ts +45 -45
  49. package/src/Utility.ts +839 -839
  50. package/src/Widget.css +8 -8
  51. package/src/Widget.ts +731 -731
  52. package/src/WidgetArray.ts +15 -15
  53. package/src/__package__.ts +3 -3
  54. package/src/index.ts +55 -55
package/src/SVGWidget.ts CHANGED
@@ -1,583 +1,583 @@
1
- import { rgb as d3Rgb } from "d3-color";
2
- import { select as d3Select } from "d3-selection";
3
- import { fontAwsesomeStyle } from "./FAChar.ts";
4
- import { svgMarkerGlitch } from "./Platform.ts";
5
- import { Transition } from "./Transition.ts";
6
- import { debounce, downloadBlob, downloadString, timestamp } from "./Utility.ts";
7
- import { ISize, Widget } from "./Widget.ts";
8
-
9
- type Point = { x: number, y: number };
10
- type Rect = { x: number, y: number, width: number, height: number };
11
-
12
- const lerp = function (point: Point, that: Point, t: number): Point {
13
- // From https://github.com/thelonious/js-intersections
14
- return {
15
- x: point.x + (that.x - point.x) * t,
16
- y: point.y + (that.y - point.y) * t
17
- };
18
- };
19
-
20
- type LineIntersection = { type: "Intersection" | "No Intersection" | "Coincident" | "Parallel", points: Point[] };
21
- const intersectLineLine = function (a1: Point, a2: Point, b1: Point, b2: Point): LineIntersection {
22
- // From https://github.com/thelonious/js-intersections
23
- const result: LineIntersection = { type: "Parallel", points: [] };
24
- const uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
25
- const ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
26
- const uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
27
-
28
- if (uB !== 0) {
29
- const ua = uaT / uB;
30
- const ub = ubT / uB;
31
-
32
- if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
33
- result.type = "Intersection";
34
- result.points.push({
35
- x: a1.x + ua * (a2.x - a1.x),
36
- y: a1.y + ua * (a2.y - a1.y)
37
- });
38
- } else {
39
- result.type = "No Intersection";
40
- }
41
- } else {
42
- if (uaT === 0 || ubT === 0) {
43
- result.type = "Coincident";
44
- } else {
45
- result.type = "Parallel";
46
- }
47
- }
48
-
49
- return result;
50
- };
51
-
52
- type CircleIntersection = { type: "Outside" | "Tangent" | "Inside" | "Intersection", points: Point[] };
53
- const intersectCircleLine = function (c: Point, r: number, a1: Point, a2: Point): CircleIntersection {
54
- // From https://github.com/thelonious/js-intersections
55
- const result: CircleIntersection = { type: "Intersection", points: [] };
56
- const a = (a2.x - a1.x) * (a2.x - a1.x) +
57
- (a2.y - a1.y) * (a2.y - a1.y);
58
- const b = 2 * ((a2.x - a1.x) * (a1.x - c.x) +
59
- (a2.y - a1.y) * (a1.y - c.y));
60
- const cc = c.x * c.x + c.y * c.y + a1.x * a1.x + a1.y * a1.y -
61
- 2 * (c.x * a1.x + c.y * a1.y) - r * r;
62
- const deter = b * b - 4 * a * cc;
63
-
64
- if (deter < 0) {
65
- result.type = "Outside";
66
- } else if (deter === 0) {
67
- result.type = "Tangent";
68
- // NOTE: should calculate this point
69
- } else {
70
- const e = Math.sqrt(deter);
71
- const u1 = (-b + e) / (2 * a);
72
- const u2 = (-b - e) / (2 * a);
73
-
74
- if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) {
75
- if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1)) {
76
- result.type = "Outside";
77
- } else {
78
- result.type = "Inside";
79
- }
80
- } else {
81
- result.type = "Intersection";
82
-
83
- if (0 <= u1 && u1 <= 1)
84
- result.points.push(lerp(a1, a2, u1));
85
-
86
- if (0 <= u2 && u2 <= 1)
87
- result.points.push(lerp(a1, a2, u2));
88
- }
89
- }
90
-
91
- return result;
92
- };
93
-
94
- export class SVGGlowFilter {
95
- protected id;
96
- protected filter;
97
- protected feOffset;
98
- protected feColorMatrix;
99
- protected feGaussianBlur;
100
- protected feBlend;
101
-
102
- constructor(target, id: string) {
103
- this.id = id;
104
- this.filter = target.append("filter")
105
- .attr("id", id)
106
- .attr("width", "130%")
107
- .attr("height", "130%");
108
- this.feOffset = this.filter.append("feOffset")
109
- .attr("result", "offOut")
110
- .attr("in", "SourceGraphic")
111
- .attr("dx", "0")
112
- .attr("dy", "0");
113
- this.feColorMatrix = this.filter.append("feColorMatrix")
114
- .attr("result", "matrixOut")
115
- .attr("in", "offOut")
116
- .attr("type", "matrix")
117
- .attr("values", this.rgb2ColorMatrix("red"))
118
- ;
119
- this.feGaussianBlur = this.filter.append("feGaussianBlur")
120
- .attr("result", "blurOut")
121
- .attr("in", "matrixOut")
122
- .attr("stdDeviation", "3")
123
- ;
124
- this.feBlend = this.filter.append("feBlend")
125
- .attr("in", "SourceGraphic")
126
- .attr("in2", "blurOut")
127
- .attr("mode", "normal")
128
- ;
129
- }
130
-
131
- rgb2ColorMatrix(color: string): string {
132
- const rgb = d3Rgb(color);
133
- return [
134
- rgb.r / 255, 0, 0, 0, rgb.r ? 1 : 0,
135
- 0, rgb.g / 255, 0, 0, rgb.g ? 1 : 0,
136
- 0, 0, rgb.b / 255, 0, rgb.b ? 1 : 0,
137
- 0, 0, 0, 1, 0
138
- ].join(" ");
139
- }
140
-
141
- enable(enable: boolean) {
142
- this.filter.attr("id", enable ? this.id : `disabled_${this.id}`);
143
- return this;
144
- }
145
-
146
- update(color: string) {
147
- this.feColorMatrix.attr("values", this.rgb2ColorMatrix(color));
148
- return this;
149
- }
150
- }
151
-
152
- export class SVGWidget extends Widget {
153
- static _class = "common_SVGWidget";
154
-
155
- protected _boundingBox;
156
- protected transition;
157
- protected _drawStartPos: "center" | "origin";
158
- protected _svgSelectionFilter: SVGGlowFilter;
159
- protected _parentRelativeDiv;
160
- protected _parentOverlay;
161
-
162
- constructor() {
163
- super();
164
-
165
- this._tag = "g";
166
-
167
- this._boundingBox = null;
168
-
169
- this.transition = new Transition(this);
170
-
171
- this._drawStartPos = "center";
172
- }
173
-
174
- // Properties ---
175
- move(_?, transitionDuration?) {
176
- const retVal = this.pos(_);
177
- if (arguments.length) {
178
- (transitionDuration ? this._element.transition().duration(transitionDuration) : this._element)
179
- .attr("transform", `translate(${_.x} ${_.y})scale(${this._widgetScale})`)
180
- ;
181
- }
182
- return retVal;
183
- }
184
-
185
- _enableOverflow = false;
186
- enableOverflow(): boolean;
187
- enableOverflow(_: boolean): this;
188
- enableOverflow(_?: boolean): boolean | this {
189
- if (!arguments.length) return this._enableOverflow;
190
- this._enableOverflow = _;
191
- return this;
192
- }
193
-
194
- _enableOverflowScroll = true;
195
- enableOverflowScroll(): boolean;
196
- enableOverflowScroll(_: boolean): this;
197
- enableOverflowScroll(_?: boolean): boolean | this {
198
- if (!arguments.length) return this._enableOverflowScroll;
199
- this._enableOverflowScroll = _;
200
- return this;
201
- }
202
-
203
- size(): ISize;
204
- size(_): this;
205
- size(_?): ISize | this {
206
- const retVal = super.size.apply(this, arguments);
207
- if (arguments.length) {
208
- this._boundingBox = null;
209
- }
210
- return retVal;
211
- }
212
-
213
- resize(_size?: { width: number, height: number }) {
214
- const retVal = super.resize.apply(this, arguments);
215
- if (this._parentRelativeDiv) {
216
- this._parentRelativeDiv
217
- .style("width", this._size.width + "px")
218
- .style("height", this._size.height + "px")
219
- ;
220
- switch (this._drawStartPos) {
221
- case "origin":
222
- this.pos({
223
- x: 0,
224
- y: 0
225
- });
226
- break;
227
- case "center":
228
- /* falls through */
229
- default:
230
- this.pos({
231
- x: this._size.width / 2,
232
- y: this._size.height / 2
233
- });
234
- break;
235
- }
236
- }
237
- if (!isNaN(this._size.width)) this._placeholderElement.attr("width", this._size.width);
238
- if (!isNaN(this._size.height)) this._placeholderElement.attr("height", this._size.height);
239
- return retVal;
240
- }
241
- // Glow Highlighting ---
242
- svgGlowID(): string {
243
- return `sel${this.id()}_glow`;
244
- }
245
-
246
- target(): null | HTMLElement | SVGElement;
247
- target(_: null | string | HTMLElement | SVGElement): this;
248
- target(_?: null | string | HTMLElement | SVGElement): null | HTMLElement | SVGElement | this {
249
- const retVal = super.target.apply(this, arguments);
250
- if (arguments.length) {
251
- if (this._target instanceof SVGElement) {
252
- this._isRootNode = false;
253
- this._placeholderElement = d3Select(this._target);
254
- this._parentWidget = this._placeholderElement.datum();
255
- if (!this._parentWidget || this._parentWidget._id === this._id) {
256
- this._parentWidget = this.locateParentWidget(this._target.parentNode);
257
- }
258
- this._parentOverlay = this.locateOverlayNode();
259
- const svg = this.locateSVGNode(this._target);
260
- const svgDefs = d3Select(svg).select<SVGDefsElement>("defs");
261
- this._svgSelectionFilter = new SVGGlowFilter(svgDefs, this.svgGlowID());
262
- } else if (this._target) {
263
- // Target is a DOM Node, so create a SVG Element ---
264
- this._parentRelativeDiv = d3Select(this._target).append("div")
265
- .style("position", "relative")
266
- ;
267
- this._placeholderElement = this._parentRelativeDiv.append("svg")
268
- .style("position", "absolute")
269
- .style("top", "0px")
270
- .style("left", "0px")
271
- ;
272
- const svgDefs = this._placeholderElement.append("defs");
273
- this._svgSelectionFilter = new SVGGlowFilter(svgDefs, this.svgGlowID());
274
- this._parentOverlay = this._parentRelativeDiv.append("div")
275
- .style("position", "absolute")
276
- .style("top", "0px")
277
- .style("left", "0px")
278
- ;
279
- if (this._size.width && this._size.height) {
280
- this.resize(this._size);
281
- } else {
282
- this.resize({ width: 0, height: 0 });
283
- }
284
- }
285
- }
286
- return retVal;
287
- }
288
-
289
- get parentRelativeDiv() {
290
- return this._parentRelativeDiv;
291
- }
292
-
293
- parentOverlay() {
294
- return this._parentOverlay;
295
- }
296
-
297
- enter(domNode, element) {
298
- super.enter(domNode, element);
299
- }
300
-
301
- update(domNode, element) {
302
- super.update(domNode, element);
303
- if (this._svgSelectionFilter) {
304
- this._svgSelectionFilter
305
- .enable(this.selectionGlow())
306
- .update(this.selectionGlowColor())
307
- ;
308
- }
309
- }
310
-
311
- postUpdate(domNode, element) {
312
- super.postUpdate(domNode, element);
313
- let transX;
314
- let transY;
315
- if (this._drawStartPos === "origin" && this._target instanceof SVGElement) {
316
- transX = (this._pos.x - this._size.width / 2);
317
- transY = (this._pos.y - this._size.height / 2);
318
- this._element.attr("transform", "translate(" + transX + "," + transY + ")scale(" + this._widgetScale + ")");
319
- } else {
320
- transX = this._pos.x;
321
- transY = this._pos.y;
322
- if (this._enableOverflow) {
323
- // Individual Widgets will need to size and position themselves corrrectly (and have calculated a BBox) ---
324
- if ((transX < 0 || transY < 0) && this._boundingBox) {
325
- transX = transX < 0 ? 0 : transX;
326
- transY = transY < 0 ? 0 : transY;
327
- if (this._enableOverflowScroll) {
328
- this._parentRelativeDiv.style("overflow", "scroll");
329
- }
330
- this._placeholderElement.attr("width", this._boundingBox.width);
331
- this._placeholderElement.attr("height", this._boundingBox.height);
332
- } else {
333
- this._parentRelativeDiv.style("overflow", null);
334
- }
335
- }
336
- this._element.attr("transform", "translate(" + transX + "," + transY + ")scale(" + this._widgetScale + ")");
337
- }
338
- }
339
-
340
- exit(domNode?, element?) {
341
- if (this._parentRelativeDiv) {
342
- this._parentOverlay.remove();
343
- this._placeholderElement.remove();
344
- this._parentRelativeDiv.remove();
345
- }
346
- super.exit(domNode, element);
347
- }
348
-
349
- getOffsetPos(): Point {
350
- let retVal = { x: 0, y: 0 };
351
- if (this._parentWidget) {
352
- retVal = this._parentWidget.getOffsetPos();
353
- retVal.x += this._pos.x;
354
- retVal.y += this._pos.y;
355
- return retVal;
356
- }
357
- return retVal;
358
- }
359
-
360
- getBBox(refresh = false, round = false): Rect {
361
- if (refresh || this._boundingBox === null) {
362
- const svgNode: SVGElement = this._element.node();
363
- if (svgNode instanceof SVGElement) {
364
- this._boundingBox = (svgNode as any).getBBox();
365
- }
366
- }
367
- if (this._boundingBox === null) {
368
- return {
369
- x: 0,
370
- y: 0,
371
- width: 0,
372
- height: 0
373
- };
374
- }
375
- return {
376
- x: (round ? Math.round(this._boundingBox.x) : this._boundingBox.x) * this._widgetScale,
377
- y: (round ? Math.round(this._boundingBox.y) : this._boundingBox.y) * this._widgetScale,
378
- width: (round ? Math.round(this._boundingBox.width) : this._boundingBox.width) * this._widgetScale,
379
- height: (round ? Math.round(this._boundingBox.height) : this._boundingBox.height) * this._widgetScale
380
- };
381
- }
382
-
383
- // Intersections ---
384
- contains(point: Point): boolean {
385
- return this.containsRect(point);
386
- }
387
-
388
- containsRect(point: Point): boolean {
389
- const size = this.getBBox();
390
- return point.x >= size.x && point.x <= size.x + size.width && point.y >= size.y && point.y <= size.y + size.height;
391
- }
392
-
393
- containsCircle(radius: number, point: Point) {
394
- const center = this.getOffsetPos();
395
- return this.distance(center, point) <= radius;
396
- }
397
-
398
- intersection(pointA: Point, pointB: Point): Point | null {
399
- return this.intersectRect(pointA, pointB);
400
- }
401
-
402
- intersectRect(pointA: Point, pointB: Point): Point | null {
403
- const center = this.getOffsetPos();
404
- const size = this.getBBox();
405
- if (pointA.x === pointB.x && pointA.y === pointB.y) {
406
- return pointA;
407
- }
408
- const TL = { x: center.x - size.width / 2, y: center.y - size.height / 2 };
409
- const TR = { x: center.x + size.width / 2, y: center.y - size.height / 2 };
410
- const BR = { x: center.x + size.width / 2, y: center.y + size.height / 2 };
411
- const BL = { x: center.x - size.width / 2, y: center.y + size.height / 2 };
412
- let intersection = intersectLineLine(TL, TR, pointA, pointB);
413
- if (intersection.points.length) {
414
- return { x: intersection.points[0].x, y: intersection.points[0].y };
415
- }
416
- intersection = intersectLineLine(TR, BR, pointA, pointB);
417
- if (intersection.points.length) {
418
- return { x: intersection.points[0].x, y: intersection.points[0].y };
419
- }
420
- intersection = intersectLineLine(BR, BL, pointA, pointB);
421
- if (intersection.points.length) {
422
- return { x: intersection.points[0].x, y: intersection.points[0].y };
423
- }
424
- intersection = intersectLineLine(BL, TL, pointA, pointB);
425
- if (intersection.points.length) {
426
- return { x: intersection.points[0].x, y: intersection.points[0].y };
427
- }
428
- return null;
429
- }
430
-
431
- intersectRectRect(rect1: Rect, rect2: Rect): Rect {
432
- const x = Math.max(rect1.x, rect2.x);
433
- const y = Math.max(rect1.y, rect2.y);
434
- const xLimit = (rect1.x < rect2.x) ? Math.min(rect1.x + rect1.width, rect2.x + rect2.width) : Math.min(rect2.x + rect2.width, rect1.x + rect1.width);
435
- const yLimit = (rect1.y < rect2.y) ? Math.min(rect1.y + rect1.height, rect2.y + rect2.height) : Math.min(rect2.y + rect2.height, rect1.y + rect1.height);
436
- return {
437
- x,
438
- y,
439
- width: xLimit - x,
440
- height: yLimit - y
441
- };
442
- }
443
-
444
- intersectCircle(radius: number, pointA: Point, pointB: Point): Point | null {
445
- const center = this.getOffsetPos();
446
- const intersection = intersectCircleLine(center, radius, pointA, pointB);
447
- if (intersection.points.length) {
448
- return { x: intersection.points[0].x, y: intersection.points[0].y };
449
- }
450
- return null;
451
- }
452
-
453
- distance(pointA: Point, pointB: Point): number {
454
- return Math.sqrt((pointA.x - pointB.x) * (pointA.x - pointB.x) + (pointA.y - pointB.y) * (pointA.y - pointB.y));
455
- }
456
-
457
- // Download ---
458
- serializeSVG(extraStyles: string = fontAwsesomeStyle): string {
459
- const origSvg = this.locateSVGNode(this._element.node());
460
- const cloneSVG = origSvg.cloneNode(true) as SVGSVGElement;
461
- const origNodes = d3Select(origSvg).selectAll("*").nodes();
462
- d3Select(cloneSVG).selectAll("*").each(function (this: SVGElement, d, i) {
463
- const compStyles = window.getComputedStyle(origNodes[i] as SVGElement);
464
- for (let i = 0; i < compStyles.length; ++i) {
465
- const styleName = compStyles.item(i);
466
- const styleValue = compStyles.getPropertyValue(styleName);
467
- const stylePriority = compStyles.getPropertyPriority(styleName);
468
- this.style.setProperty(styleName, styleValue, stylePriority);
469
- }
470
- });
471
-
472
- if (extraStyles) {
473
- const defs = cloneSVG.getElementsByTagName("defs");
474
- if (defs.length) {
475
- const extraStyle = document.createElement("style");
476
- extraStyle.setAttribute("type", "text/css");
477
- extraStyle.innerText = extraStyles;
478
- defs[0].appendChild(extraStyle);
479
- }
480
- }
481
-
482
- const serializer = new XMLSerializer();
483
- return serializer.serializeToString(cloneSVG);
484
- }
485
-
486
- toBlob(extraStyles: string = fontAwsesomeStyle): Blob {
487
- return new Blob([this.serializeSVG(extraStyles)], { type: "image/svg+xml" });
488
- }
489
-
490
- rasterize(extraStyles: string = fontAwsesomeStyle, ...extraWidgets: SVGWidget[]): Promise<Blob> {
491
- const widgets = [this, ...extraWidgets];
492
- const sizes = widgets.map(widget => widget.locateSVGNode(widget.element().node()).getBoundingClientRect());
493
- const width = sizes.reduce((prev, curr) => prev + curr.width, 0);
494
- const height = Math.max(...sizes.map(s => s.height));
495
-
496
- const canvas = document.createElement("canvas");
497
- canvas.width = width;
498
- canvas.height = height;
499
- canvas.style.width = width + "px";
500
- const ctx = canvas.getContext("2d");
501
- ctx.fillStyle = "white";
502
- ctx.fillRect(0, 0, width, height);
503
- ctx.fillStyle = "transparent";
504
- return new Promise((resolve, reject) => {
505
- let xPos = 0;
506
- Promise.all(widgets.map((widget, i) => {
507
- const x = xPos;
508
- const y = (height - sizes[i].height) / 2;
509
- xPos += sizes[i].width;
510
- return new Promise<void>((resolve, reject) => {
511
- const image = new Image();
512
- image.onerror = reject;
513
- image.onload = () => {
514
- ctx.drawImage(image, 0, 0, sizes[i].width, sizes[i].height, x, y, sizes[i].width, sizes[i].height);
515
- resolve();
516
- };
517
- image.src = URL.createObjectURL(widget.toBlob(extraStyles));
518
- });
519
- })).then(() => {
520
- ctx.canvas.toBlob(resolve); // Not supported by Edge browser
521
- });
522
- });
523
- }
524
-
525
- downloadSVG(extraStyles: string = fontAwsesomeStyle) {
526
- downloadString("SVG", this.serializeSVG(extraStyles));
527
- }
528
-
529
- downloadPNG(filename: string = `image_${timestamp()}`, extraStyles: string = fontAwsesomeStyle, ...extraWidgets: SVGWidget[]) {
530
- this.rasterize(extraStyles, ...extraWidgets).then(blob => downloadBlob(blob, `${filename}.png`));
531
- }
532
-
533
- // IE Fixers ---
534
- _pushMarkers(element?) {
535
- if (svgMarkerGlitch) {
536
- element = element || this._element;
537
- element.selectAll("path[marker-start],path[marker-end]")
538
- .attr("fixme-start", function () { return this.getAttribute("marker-start"); })
539
- .attr("fixme-end", function () { return this.getAttribute("marker-end"); })
540
- .attr("marker-start", null)
541
- .attr("marker-end", null)
542
- ;
543
- }
544
- }
545
-
546
- _popMarkers(element?) {
547
- if (svgMarkerGlitch) {
548
- element = element || this._element;
549
- element.selectAll("path[fixme-start],path[fixme-end]")
550
- .attr("marker-start", function () {
551
- return this.getAttribute("fixme-start");
552
- })
553
- .attr("marker-end", function () { return this.getAttribute("fixme-end"); })
554
- .attr("fixme-start", null)
555
- .attr("fixme-end", null)
556
- ;
557
- }
558
- }
559
-
560
- _popMarkersDebounced = debounce(function (element) {
561
- if (svgMarkerGlitch) {
562
- this._popMarkers(element);
563
- }
564
- }, 250);
565
-
566
- _fixIEMarkers(element?) {
567
- if (svgMarkerGlitch) {
568
- this._pushMarkers(element);
569
- this._popMarkersDebounced(element);
570
- }
571
- }
572
- }
573
- SVGWidget.prototype._class += " common_SVGWidget";
574
-
575
- export interface SVGWidget {
576
- selectionGlow(): boolean;
577
- selectionGlow(_: boolean): this;
578
- selectionGlowColor(): string;
579
- selectionGlowColor(_: string): this;
580
- }
581
-
582
- SVGWidget.prototype.publish("selectionGlow", true, "boolean", "Selection Glow");
583
- SVGWidget.prototype.publish("selectionGlowColor", "red", "html-color", "Selection Glow Color");
1
+ import { rgb as d3Rgb } from "d3-color";
2
+ import { select as d3Select } from "d3-selection";
3
+ import { fontAwsesomeStyle } from "./FAChar.ts";
4
+ import { svgMarkerGlitch } from "./Platform.ts";
5
+ import { Transition } from "./Transition.ts";
6
+ import { debounce, downloadBlob, downloadString, timestamp } from "./Utility.ts";
7
+ import { ISize, Widget } from "./Widget.ts";
8
+
9
+ type Point = { x: number, y: number };
10
+ type Rect = { x: number, y: number, width: number, height: number };
11
+
12
+ const lerp = function (point: Point, that: Point, t: number): Point {
13
+ // From https://github.com/thelonious/js-intersections
14
+ return {
15
+ x: point.x + (that.x - point.x) * t,
16
+ y: point.y + (that.y - point.y) * t
17
+ };
18
+ };
19
+
20
+ type LineIntersection = { type: "Intersection" | "No Intersection" | "Coincident" | "Parallel", points: Point[] };
21
+ const intersectLineLine = function (a1: Point, a2: Point, b1: Point, b2: Point): LineIntersection {
22
+ // From https://github.com/thelonious/js-intersections
23
+ const result: LineIntersection = { type: "Parallel", points: [] };
24
+ const uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
25
+ const ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
26
+ const uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
27
+
28
+ if (uB !== 0) {
29
+ const ua = uaT / uB;
30
+ const ub = ubT / uB;
31
+
32
+ if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
33
+ result.type = "Intersection";
34
+ result.points.push({
35
+ x: a1.x + ua * (a2.x - a1.x),
36
+ y: a1.y + ua * (a2.y - a1.y)
37
+ });
38
+ } else {
39
+ result.type = "No Intersection";
40
+ }
41
+ } else {
42
+ if (uaT === 0 || ubT === 0) {
43
+ result.type = "Coincident";
44
+ } else {
45
+ result.type = "Parallel";
46
+ }
47
+ }
48
+
49
+ return result;
50
+ };
51
+
52
+ type CircleIntersection = { type: "Outside" | "Tangent" | "Inside" | "Intersection", points: Point[] };
53
+ const intersectCircleLine = function (c: Point, r: number, a1: Point, a2: Point): CircleIntersection {
54
+ // From https://github.com/thelonious/js-intersections
55
+ const result: CircleIntersection = { type: "Intersection", points: [] };
56
+ const a = (a2.x - a1.x) * (a2.x - a1.x) +
57
+ (a2.y - a1.y) * (a2.y - a1.y);
58
+ const b = 2 * ((a2.x - a1.x) * (a1.x - c.x) +
59
+ (a2.y - a1.y) * (a1.y - c.y));
60
+ const cc = c.x * c.x + c.y * c.y + a1.x * a1.x + a1.y * a1.y -
61
+ 2 * (c.x * a1.x + c.y * a1.y) - r * r;
62
+ const deter = b * b - 4 * a * cc;
63
+
64
+ if (deter < 0) {
65
+ result.type = "Outside";
66
+ } else if (deter === 0) {
67
+ result.type = "Tangent";
68
+ // NOTE: should calculate this point
69
+ } else {
70
+ const e = Math.sqrt(deter);
71
+ const u1 = (-b + e) / (2 * a);
72
+ const u2 = (-b - e) / (2 * a);
73
+
74
+ if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) {
75
+ if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1)) {
76
+ result.type = "Outside";
77
+ } else {
78
+ result.type = "Inside";
79
+ }
80
+ } else {
81
+ result.type = "Intersection";
82
+
83
+ if (0 <= u1 && u1 <= 1)
84
+ result.points.push(lerp(a1, a2, u1));
85
+
86
+ if (0 <= u2 && u2 <= 1)
87
+ result.points.push(lerp(a1, a2, u2));
88
+ }
89
+ }
90
+
91
+ return result;
92
+ };
93
+
94
+ export class SVGGlowFilter {
95
+ protected id;
96
+ protected filter;
97
+ protected feOffset;
98
+ protected feColorMatrix;
99
+ protected feGaussianBlur;
100
+ protected feBlend;
101
+
102
+ constructor(target, id: string) {
103
+ this.id = id;
104
+ this.filter = target.append("filter")
105
+ .attr("id", id)
106
+ .attr("width", "130%")
107
+ .attr("height", "130%");
108
+ this.feOffset = this.filter.append("feOffset")
109
+ .attr("result", "offOut")
110
+ .attr("in", "SourceGraphic")
111
+ .attr("dx", "0")
112
+ .attr("dy", "0");
113
+ this.feColorMatrix = this.filter.append("feColorMatrix")
114
+ .attr("result", "matrixOut")
115
+ .attr("in", "offOut")
116
+ .attr("type", "matrix")
117
+ .attr("values", this.rgb2ColorMatrix("red"))
118
+ ;
119
+ this.feGaussianBlur = this.filter.append("feGaussianBlur")
120
+ .attr("result", "blurOut")
121
+ .attr("in", "matrixOut")
122
+ .attr("stdDeviation", "3")
123
+ ;
124
+ this.feBlend = this.filter.append("feBlend")
125
+ .attr("in", "SourceGraphic")
126
+ .attr("in2", "blurOut")
127
+ .attr("mode", "normal")
128
+ ;
129
+ }
130
+
131
+ rgb2ColorMatrix(color: string): string {
132
+ const rgb = d3Rgb(color);
133
+ return [
134
+ rgb.r / 255, 0, 0, 0, rgb.r ? 1 : 0,
135
+ 0, rgb.g / 255, 0, 0, rgb.g ? 1 : 0,
136
+ 0, 0, rgb.b / 255, 0, rgb.b ? 1 : 0,
137
+ 0, 0, 0, 1, 0
138
+ ].join(" ");
139
+ }
140
+
141
+ enable(enable: boolean) {
142
+ this.filter.attr("id", enable ? this.id : `disabled_${this.id}`);
143
+ return this;
144
+ }
145
+
146
+ update(color: string) {
147
+ this.feColorMatrix.attr("values", this.rgb2ColorMatrix(color));
148
+ return this;
149
+ }
150
+ }
151
+
152
+ export class SVGWidget extends Widget {
153
+ static _class = "common_SVGWidget";
154
+
155
+ protected _boundingBox;
156
+ protected transition;
157
+ protected _drawStartPos: "center" | "origin";
158
+ protected _svgSelectionFilter: SVGGlowFilter;
159
+ protected _parentRelativeDiv;
160
+ protected _parentOverlay;
161
+
162
+ constructor() {
163
+ super();
164
+
165
+ this._tag = "g";
166
+
167
+ this._boundingBox = null;
168
+
169
+ this.transition = new Transition(this);
170
+
171
+ this._drawStartPos = "center";
172
+ }
173
+
174
+ // Properties ---
175
+ move(_?, transitionDuration?) {
176
+ const retVal = this.pos(_);
177
+ if (arguments.length) {
178
+ (transitionDuration ? this._element.transition().duration(transitionDuration) : this._element)
179
+ .attr("transform", `translate(${_.x} ${_.y})scale(${this._widgetScale})`)
180
+ ;
181
+ }
182
+ return retVal;
183
+ }
184
+
185
+ _enableOverflow = false;
186
+ enableOverflow(): boolean;
187
+ enableOverflow(_: boolean): this;
188
+ enableOverflow(_?: boolean): boolean | this {
189
+ if (!arguments.length) return this._enableOverflow;
190
+ this._enableOverflow = _;
191
+ return this;
192
+ }
193
+
194
+ _enableOverflowScroll = true;
195
+ enableOverflowScroll(): boolean;
196
+ enableOverflowScroll(_: boolean): this;
197
+ enableOverflowScroll(_?: boolean): boolean | this {
198
+ if (!arguments.length) return this._enableOverflowScroll;
199
+ this._enableOverflowScroll = _;
200
+ return this;
201
+ }
202
+
203
+ size(): ISize;
204
+ size(_): this;
205
+ size(_?): ISize | this {
206
+ const retVal = super.size.apply(this, arguments);
207
+ if (arguments.length) {
208
+ this._boundingBox = null;
209
+ }
210
+ return retVal;
211
+ }
212
+
213
+ resize(_size?: { width: number, height: number }) {
214
+ const retVal = super.resize.apply(this, arguments);
215
+ if (this._parentRelativeDiv) {
216
+ this._parentRelativeDiv
217
+ .style("width", this._size.width + "px")
218
+ .style("height", this._size.height + "px")
219
+ ;
220
+ switch (this._drawStartPos) {
221
+ case "origin":
222
+ this.pos({
223
+ x: 0,
224
+ y: 0
225
+ });
226
+ break;
227
+ case "center":
228
+ /* falls through */
229
+ default:
230
+ this.pos({
231
+ x: this._size.width / 2,
232
+ y: this._size.height / 2
233
+ });
234
+ break;
235
+ }
236
+ }
237
+ if (!isNaN(this._size.width)) this._placeholderElement.attr("width", this._size.width);
238
+ if (!isNaN(this._size.height)) this._placeholderElement.attr("height", this._size.height);
239
+ return retVal;
240
+ }
241
+ // Glow Highlighting ---
242
+ svgGlowID(): string {
243
+ return `sel${this.id()}_glow`;
244
+ }
245
+
246
+ target(): null | HTMLElement | SVGElement;
247
+ target(_: null | string | HTMLElement | SVGElement): this;
248
+ target(_?: null | string | HTMLElement | SVGElement): null | HTMLElement | SVGElement | this {
249
+ const retVal = super.target.apply(this, arguments);
250
+ if (arguments.length) {
251
+ if (this._target instanceof SVGElement) {
252
+ this._isRootNode = false;
253
+ this._placeholderElement = d3Select(this._target);
254
+ this._parentWidget = this._placeholderElement.datum();
255
+ if (!this._parentWidget || this._parentWidget._id === this._id) {
256
+ this._parentWidget = this.locateParentWidget(this._target.parentNode);
257
+ }
258
+ this._parentOverlay = this.locateOverlayNode();
259
+ const svg = this.locateSVGNode(this._target);
260
+ const svgDefs = d3Select(svg).select<SVGDefsElement>("defs");
261
+ this._svgSelectionFilter = new SVGGlowFilter(svgDefs, this.svgGlowID());
262
+ } else if (this._target) {
263
+ // Target is a DOM Node, so create a SVG Element ---
264
+ this._parentRelativeDiv = d3Select(this._target).append("div")
265
+ .style("position", "relative")
266
+ ;
267
+ this._placeholderElement = this._parentRelativeDiv.append("svg")
268
+ .style("position", "absolute")
269
+ .style("top", "0px")
270
+ .style("left", "0px")
271
+ ;
272
+ const svgDefs = this._placeholderElement.append("defs");
273
+ this._svgSelectionFilter = new SVGGlowFilter(svgDefs, this.svgGlowID());
274
+ this._parentOverlay = this._parentRelativeDiv.append("div")
275
+ .style("position", "absolute")
276
+ .style("top", "0px")
277
+ .style("left", "0px")
278
+ ;
279
+ if (this._size.width && this._size.height) {
280
+ this.resize(this._size);
281
+ } else {
282
+ this.resize({ width: 0, height: 0 });
283
+ }
284
+ }
285
+ }
286
+ return retVal;
287
+ }
288
+
289
+ get parentRelativeDiv() {
290
+ return this._parentRelativeDiv;
291
+ }
292
+
293
+ parentOverlay() {
294
+ return this._parentOverlay;
295
+ }
296
+
297
+ enter(domNode, element) {
298
+ super.enter(domNode, element);
299
+ }
300
+
301
+ update(domNode, element) {
302
+ super.update(domNode, element);
303
+ if (this._svgSelectionFilter) {
304
+ this._svgSelectionFilter
305
+ .enable(this.selectionGlow())
306
+ .update(this.selectionGlowColor())
307
+ ;
308
+ }
309
+ }
310
+
311
+ postUpdate(domNode, element) {
312
+ super.postUpdate(domNode, element);
313
+ let transX;
314
+ let transY;
315
+ if (this._drawStartPos === "origin" && this._target instanceof SVGElement) {
316
+ transX = (this._pos.x - this._size.width / 2);
317
+ transY = (this._pos.y - this._size.height / 2);
318
+ this._element.attr("transform", "translate(" + transX + "," + transY + ")scale(" + this._widgetScale + ")");
319
+ } else {
320
+ transX = this._pos.x;
321
+ transY = this._pos.y;
322
+ if (this._enableOverflow) {
323
+ // Individual Widgets will need to size and position themselves corrrectly (and have calculated a BBox) ---
324
+ if ((transX < 0 || transY < 0) && this._boundingBox) {
325
+ transX = transX < 0 ? 0 : transX;
326
+ transY = transY < 0 ? 0 : transY;
327
+ if (this._enableOverflowScroll) {
328
+ this._parentRelativeDiv.style("overflow", "scroll");
329
+ }
330
+ this._placeholderElement.attr("width", this._boundingBox.width);
331
+ this._placeholderElement.attr("height", this._boundingBox.height);
332
+ } else {
333
+ this._parentRelativeDiv.style("overflow", null);
334
+ }
335
+ }
336
+ this._element.attr("transform", "translate(" + transX + "," + transY + ")scale(" + this._widgetScale + ")");
337
+ }
338
+ }
339
+
340
+ exit(domNode?, element?) {
341
+ if (this._parentRelativeDiv) {
342
+ this._parentOverlay.remove();
343
+ this._placeholderElement.remove();
344
+ this._parentRelativeDiv.remove();
345
+ }
346
+ super.exit(domNode, element);
347
+ }
348
+
349
+ getOffsetPos(): Point {
350
+ let retVal = { x: 0, y: 0 };
351
+ if (this._parentWidget) {
352
+ retVal = this._parentWidget.getOffsetPos();
353
+ retVal.x += this._pos.x;
354
+ retVal.y += this._pos.y;
355
+ return retVal;
356
+ }
357
+ return retVal;
358
+ }
359
+
360
+ getBBox(refresh = false, round = false): Rect {
361
+ if (refresh || this._boundingBox === null) {
362
+ const svgNode: SVGElement = this._element.node();
363
+ if (svgNode instanceof SVGElement) {
364
+ this._boundingBox = (svgNode as any).getBBox();
365
+ }
366
+ }
367
+ if (this._boundingBox === null) {
368
+ return {
369
+ x: 0,
370
+ y: 0,
371
+ width: 0,
372
+ height: 0
373
+ };
374
+ }
375
+ return {
376
+ x: (round ? Math.round(this._boundingBox.x) : this._boundingBox.x) * this._widgetScale,
377
+ y: (round ? Math.round(this._boundingBox.y) : this._boundingBox.y) * this._widgetScale,
378
+ width: (round ? Math.round(this._boundingBox.width) : this._boundingBox.width) * this._widgetScale,
379
+ height: (round ? Math.round(this._boundingBox.height) : this._boundingBox.height) * this._widgetScale
380
+ };
381
+ }
382
+
383
+ // Intersections ---
384
+ contains(point: Point): boolean {
385
+ return this.containsRect(point);
386
+ }
387
+
388
+ containsRect(point: Point): boolean {
389
+ const size = this.getBBox();
390
+ return point.x >= size.x && point.x <= size.x + size.width && point.y >= size.y && point.y <= size.y + size.height;
391
+ }
392
+
393
+ containsCircle(radius: number, point: Point) {
394
+ const center = this.getOffsetPos();
395
+ return this.distance(center, point) <= radius;
396
+ }
397
+
398
+ intersection(pointA: Point, pointB: Point): Point | null {
399
+ return this.intersectRect(pointA, pointB);
400
+ }
401
+
402
+ intersectRect(pointA: Point, pointB: Point): Point | null {
403
+ const center = this.getOffsetPos();
404
+ const size = this.getBBox();
405
+ if (pointA.x === pointB.x && pointA.y === pointB.y) {
406
+ return pointA;
407
+ }
408
+ const TL = { x: center.x - size.width / 2, y: center.y - size.height / 2 };
409
+ const TR = { x: center.x + size.width / 2, y: center.y - size.height / 2 };
410
+ const BR = { x: center.x + size.width / 2, y: center.y + size.height / 2 };
411
+ const BL = { x: center.x - size.width / 2, y: center.y + size.height / 2 };
412
+ let intersection = intersectLineLine(TL, TR, pointA, pointB);
413
+ if (intersection.points.length) {
414
+ return { x: intersection.points[0].x, y: intersection.points[0].y };
415
+ }
416
+ intersection = intersectLineLine(TR, BR, pointA, pointB);
417
+ if (intersection.points.length) {
418
+ return { x: intersection.points[0].x, y: intersection.points[0].y };
419
+ }
420
+ intersection = intersectLineLine(BR, BL, pointA, pointB);
421
+ if (intersection.points.length) {
422
+ return { x: intersection.points[0].x, y: intersection.points[0].y };
423
+ }
424
+ intersection = intersectLineLine(BL, TL, pointA, pointB);
425
+ if (intersection.points.length) {
426
+ return { x: intersection.points[0].x, y: intersection.points[0].y };
427
+ }
428
+ return null;
429
+ }
430
+
431
+ intersectRectRect(rect1: Rect, rect2: Rect): Rect {
432
+ const x = Math.max(rect1.x, rect2.x);
433
+ const y = Math.max(rect1.y, rect2.y);
434
+ const xLimit = (rect1.x < rect2.x) ? Math.min(rect1.x + rect1.width, rect2.x + rect2.width) : Math.min(rect2.x + rect2.width, rect1.x + rect1.width);
435
+ const yLimit = (rect1.y < rect2.y) ? Math.min(rect1.y + rect1.height, rect2.y + rect2.height) : Math.min(rect2.y + rect2.height, rect1.y + rect1.height);
436
+ return {
437
+ x,
438
+ y,
439
+ width: xLimit - x,
440
+ height: yLimit - y
441
+ };
442
+ }
443
+
444
+ intersectCircle(radius: number, pointA: Point, pointB: Point): Point | null {
445
+ const center = this.getOffsetPos();
446
+ const intersection = intersectCircleLine(center, radius, pointA, pointB);
447
+ if (intersection.points.length) {
448
+ return { x: intersection.points[0].x, y: intersection.points[0].y };
449
+ }
450
+ return null;
451
+ }
452
+
453
+ distance(pointA: Point, pointB: Point): number {
454
+ return Math.sqrt((pointA.x - pointB.x) * (pointA.x - pointB.x) + (pointA.y - pointB.y) * (pointA.y - pointB.y));
455
+ }
456
+
457
+ // Download ---
458
+ serializeSVG(extraStyles: string = fontAwsesomeStyle): string {
459
+ const origSvg = this.locateSVGNode(this._element.node());
460
+ const cloneSVG = origSvg.cloneNode(true) as SVGSVGElement;
461
+ const origNodes = d3Select(origSvg).selectAll("*").nodes();
462
+ d3Select(cloneSVG).selectAll("*").each(function (this: SVGElement, d, i) {
463
+ const compStyles = window.getComputedStyle(origNodes[i] as SVGElement);
464
+ for (let i = 0; i < compStyles.length; ++i) {
465
+ const styleName = compStyles.item(i);
466
+ const styleValue = compStyles.getPropertyValue(styleName);
467
+ const stylePriority = compStyles.getPropertyPriority(styleName);
468
+ this.style.setProperty(styleName, styleValue, stylePriority);
469
+ }
470
+ });
471
+
472
+ if (extraStyles) {
473
+ const defs = cloneSVG.getElementsByTagName("defs");
474
+ if (defs.length) {
475
+ const extraStyle = document.createElement("style");
476
+ extraStyle.setAttribute("type", "text/css");
477
+ extraStyle.innerText = extraStyles;
478
+ defs[0].appendChild(extraStyle);
479
+ }
480
+ }
481
+
482
+ const serializer = new XMLSerializer();
483
+ return serializer.serializeToString(cloneSVG);
484
+ }
485
+
486
+ toBlob(extraStyles: string = fontAwsesomeStyle): Blob {
487
+ return new Blob([this.serializeSVG(extraStyles)], { type: "image/svg+xml" });
488
+ }
489
+
490
+ rasterize(extraStyles: string = fontAwsesomeStyle, ...extraWidgets: SVGWidget[]): Promise<Blob> {
491
+ const widgets = [this, ...extraWidgets];
492
+ const sizes = widgets.map(widget => widget.locateSVGNode(widget.element().node()).getBoundingClientRect());
493
+ const width = sizes.reduce((prev, curr) => prev + curr.width, 0);
494
+ const height = Math.max(...sizes.map(s => s.height));
495
+
496
+ const canvas = document.createElement("canvas");
497
+ canvas.width = width;
498
+ canvas.height = height;
499
+ canvas.style.width = width + "px";
500
+ const ctx = canvas.getContext("2d");
501
+ ctx.fillStyle = "white";
502
+ ctx.fillRect(0, 0, width, height);
503
+ ctx.fillStyle = "transparent";
504
+ return new Promise((resolve, reject) => {
505
+ let xPos = 0;
506
+ Promise.all(widgets.map((widget, i) => {
507
+ const x = xPos;
508
+ const y = (height - sizes[i].height) / 2;
509
+ xPos += sizes[i].width;
510
+ return new Promise<void>((resolve, reject) => {
511
+ const image = new Image();
512
+ image.onerror = reject;
513
+ image.onload = () => {
514
+ ctx.drawImage(image, 0, 0, sizes[i].width, sizes[i].height, x, y, sizes[i].width, sizes[i].height);
515
+ resolve();
516
+ };
517
+ image.src = URL.createObjectURL(widget.toBlob(extraStyles));
518
+ });
519
+ })).then(() => {
520
+ ctx.canvas.toBlob(resolve); // Not supported by Edge browser
521
+ });
522
+ });
523
+ }
524
+
525
+ downloadSVG(extraStyles: string = fontAwsesomeStyle) {
526
+ downloadString("SVG", this.serializeSVG(extraStyles));
527
+ }
528
+
529
+ downloadPNG(filename: string = `image_${timestamp()}`, extraStyles: string = fontAwsesomeStyle, ...extraWidgets: SVGWidget[]) {
530
+ this.rasterize(extraStyles, ...extraWidgets).then(blob => downloadBlob(blob, `${filename}.png`));
531
+ }
532
+
533
+ // IE Fixers ---
534
+ _pushMarkers(element?) {
535
+ if (svgMarkerGlitch) {
536
+ element = element || this._element;
537
+ element.selectAll("path[marker-start],path[marker-end]")
538
+ .attr("fixme-start", function () { return this.getAttribute("marker-start"); })
539
+ .attr("fixme-end", function () { return this.getAttribute("marker-end"); })
540
+ .attr("marker-start", null)
541
+ .attr("marker-end", null)
542
+ ;
543
+ }
544
+ }
545
+
546
+ _popMarkers(element?) {
547
+ if (svgMarkerGlitch) {
548
+ element = element || this._element;
549
+ element.selectAll("path[fixme-start],path[fixme-end]")
550
+ .attr("marker-start", function () {
551
+ return this.getAttribute("fixme-start");
552
+ })
553
+ .attr("marker-end", function () { return this.getAttribute("fixme-end"); })
554
+ .attr("fixme-start", null)
555
+ .attr("fixme-end", null)
556
+ ;
557
+ }
558
+ }
559
+
560
+ _popMarkersDebounced = debounce(function (element) {
561
+ if (svgMarkerGlitch) {
562
+ this._popMarkers(element);
563
+ }
564
+ }, 250);
565
+
566
+ _fixIEMarkers(element?) {
567
+ if (svgMarkerGlitch) {
568
+ this._pushMarkers(element);
569
+ this._popMarkersDebounced(element);
570
+ }
571
+ }
572
+ }
573
+ SVGWidget.prototype._class += " common_SVGWidget";
574
+
575
+ export interface SVGWidget {
576
+ selectionGlow(): boolean;
577
+ selectionGlow(_: boolean): this;
578
+ selectionGlowColor(): string;
579
+ selectionGlowColor(_: string): this;
580
+ }
581
+
582
+ SVGWidget.prototype.publish("selectionGlow", true, "boolean", "Selection Glow");
583
+ SVGWidget.prototype.publish("selectionGlowColor", "red", "html-color", "Selection Glow Color");