@hpcc-js/map 3.5.1 → 3.5.4

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 (96) hide show
  1. package/LICENSE +43 -43
  2. package/README.md +88 -88
  3. package/TopoJSON/BR.json +122 -122
  4. package/TopoJSON/GB_idx.json +1 -1
  5. package/TopoJSON/IE_idx.json +1 -1
  6. package/TopoJSON/ND_idx.json +1 -1
  7. package/TopoJSON/countries.json +257 -257
  8. package/TopoJSON/us-counties.json +16550 -16550
  9. package/TopoJSON/us-states.json +458 -458
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.umd.cjs +1 -1
  13. package/dist/index.umd.cjs.map +1 -1
  14. package/package.json +9 -9
  15. package/src/CanvasPinLayer.ts +99 -99
  16. package/src/CanvasPins.ts +397 -397
  17. package/src/Choropleth.css +26 -26
  18. package/src/Choropleth.ts +203 -203
  19. package/src/ChoroplethContinents.ts +13 -13
  20. package/src/ChoroplethCounties.ts +111 -111
  21. package/src/ChoroplethCountries.ts +100 -100
  22. package/src/ChoroplethStates.ts +103 -103
  23. package/src/ChoroplethStatesHeat.ts +8 -8
  24. package/src/GMap.css +20 -20
  25. package/src/GMap.ts +880 -880
  26. package/src/GMapCounties.ts +93 -93
  27. package/src/GMapGraph.ts +61 -61
  28. package/src/GMapHeat.ts +27 -27
  29. package/src/GMapLayered.ts +94 -94
  30. package/src/GMapPin.ts +115 -115
  31. package/src/GMapPinLine.ts +138 -138
  32. package/src/GeoHash.css +14 -14
  33. package/src/GeoHash.ts +139 -139
  34. package/src/Graph.css +9 -9
  35. package/src/Graph.ts +98 -98
  36. package/src/Graticule.css +12 -12
  37. package/src/Graticule.ts +97 -97
  38. package/src/Heat.ts +87 -87
  39. package/src/IChoropleth.ts +8 -8
  40. package/src/Layer.ts +99 -99
  41. package/src/Layered.css +18 -18
  42. package/src/Layered.ts +206 -206
  43. package/src/Lines.css +8 -8
  44. package/src/Lines.ts +78 -78
  45. package/src/OpenStreet.css +15 -15
  46. package/src/OpenStreet.ts +126 -126
  47. package/src/Pins.css +17 -17
  48. package/src/Pins.ts +350 -350
  49. package/src/Projection.ts +42 -42
  50. package/src/TestHeatMap.ts +8 -8
  51. package/src/TopoJSONChoropleth.ts +125 -125
  52. package/src/Utility.ts +484 -484
  53. package/src/__package__.ts +3 -3
  54. package/src/index.ts +33 -33
  55. package/src/leaflet/AlbersPR.ts +48 -48
  56. package/src/leaflet/Blank.ts +9 -9
  57. package/src/leaflet/Circles.ts +139 -139
  58. package/src/leaflet/ClusterCircles.css +26 -26
  59. package/src/leaflet/ClusterCircles.ts +88 -88
  60. package/src/leaflet/Countries.ts +43 -43
  61. package/src/leaflet/DrawLayer.ts +167 -167
  62. package/src/leaflet/FeatureLayer.ts +138 -138
  63. package/src/leaflet/GMap.ts +44 -44
  64. package/src/leaflet/HeatLayer.ts +77 -77
  65. package/src/leaflet/Icons.ts +60 -60
  66. package/src/leaflet/Leaflet.css +3 -3
  67. package/src/leaflet/Leaflet.ts +239 -239
  68. package/src/leaflet/MapBox.ts +35 -35
  69. package/src/leaflet/Markers.ts +109 -109
  70. package/src/leaflet/OpenStreet.ts +27 -27
  71. package/src/leaflet/Path.ts +138 -138
  72. package/src/leaflet/Pins.ts +73 -73
  73. package/src/leaflet/Polygons.ts +113 -113
  74. package/src/leaflet/Region.ts +138 -138
  75. package/src/leaflet/Text.ts +99 -99
  76. package/src/leaflet/TileLayer.ts +81 -81
  77. package/src/leaflet/TopoJSON.ts +146 -146
  78. package/src/leaflet/US.ts +15 -15
  79. package/src/leaflet/USCounties.ts +43 -43
  80. package/src/leaflet/USStates.ts +41 -41
  81. package/src/leaflet/World.css +3 -3
  82. package/src/leaflet/World.ts +172 -172
  83. package/src/leaflet/index.ts +18 -18
  84. package/src/leaflet/leaflet-shim.ts +18 -18
  85. package/src/leaflet/plugins/BeautifyIcon.css +44 -44
  86. package/src/leaflet/plugins/BeautifyIcon.ts +190 -190
  87. package/src/leaflet/plugins/BeutifyIcon.licence +20 -20
  88. package/src/leaflet/plugins/D3SvgOverlay.css +2 -2
  89. package/src/leaflet/plugins/D3SvgOverlay.licence +20 -20
  90. package/src/leaflet/plugins/D3SvgOverlay.ts +175 -175
  91. package/src/leaflet/plugins/HeatLayer.license +21 -21
  92. package/src/leaflet/plugins/HeatLayer.ts +224 -224
  93. package/src/leaflet/plugins/Leaflet.GoogleMutant.ts +424 -424
  94. package/src/leaflet/plugins/lru_map.ts +352 -352
  95. package/src/test.ts +114 -114
  96. package/types/Layered.d.ts +1 -1
package/src/CanvasPins.ts CHANGED
@@ -1,397 +1,397 @@
1
- import { CanvasWidget } from "@hpcc-js/common";
2
- import { quadtree as d3quadtree } from "d3-quadtree";
3
-
4
- export interface CanvasPinRow {
5
- 0: number;
6
- 1: number;
7
- is_cluster: boolean;
8
- already_flagged: boolean;
9
- weight: number;
10
- fillStyle: string;
11
- strokeStyle: string;
12
- overlap_arr: CanvasPinRow[];
13
- }
14
-
15
- interface Rect {
16
- left: number;
17
- right: number;
18
- top: number;
19
- bottom: number;
20
- }
21
-
22
- class Quadtree {
23
- protected _pin_h = 0;
24
- protected _pin_w = 0;
25
- protected _tree: any;
26
- constructor(extent: any, raw_data: any, pin_h: number, pin_w: number) {
27
- this._pin_h = pin_h;
28
- this._pin_w = pin_w;
29
- this._tree = d3quadtree()
30
- .extent(extent)
31
- .addAll(raw_data);
32
- }
33
-
34
- searchRect(left: number, top: number, right: number, bottom: number): CanvasPinRow[] {
35
- const ret: any = [];
36
- this._tree.visit((node: any, x1: number, y1: number, x2: number, y2: number) => {
37
- let next_exists = false;
38
- do {
39
- if (!node.length) {
40
- if (node.data && !node.data.already_flagged) {
41
- const is_overlapping = overlaps({ left, right, top, bottom }, node.data);
42
- if (is_overlapping) {
43
- node.data.already_flagged = true;
44
- ret.push(node.data);
45
- }
46
- }
47
- }
48
- next_exists = node = node.next;
49
- } while (next_exists);
50
- return x1 >= right || y1 >= bottom || x2 < left || y2 < top;
51
- });
52
- return ret;
53
- function overlaps(r1: Rect, point: CanvasPinRow) {
54
- return (point[0] < r1.right || point[0] > r1.left) && (point[1] < r1.bottom || point[1] > r1.top);
55
- }
56
- }
57
- getTreeRects(): any {
58
- const ret = [];
59
- this._tree.visit((node: any, x1: number, y1: number, x2: number, y2: number) => {
60
- ret.push([x1, y1, x2 - x1, y2 - y1]);
61
- });
62
- return ret;
63
- }
64
- }
65
-
66
- export class CanvasPins extends CanvasWidget {
67
- _quadtree_rect_arr;
68
- _drawData;
69
- _overlap_count = 0;
70
- _sub_overlap_count = 0;
71
-
72
- constructor() {
73
- super();
74
- }
75
-
76
- enter(domNode, element) {
77
- super.enter.apply(this, arguments);
78
- this.resize(this._size);
79
- }
80
-
81
- update(domNode, element) {
82
- super.update.apply(this, arguments);
83
- this._ctx = this.element().node().getContext("2d");
84
- const needs_data_skew = this.topLeftX_exists() && this.topLeftY_exists() && this.bottomRightX_exists() && this.bottomRightY_exists();
85
- const _data = (needs_data_skew ? this.skewedData() : this.data())
86
- .map(row => [...row])
87
- .map(row => {
88
- row.is_cluster = false;
89
- row.already_flagged = false;
90
- row.weight = row[2];
91
- row.fillStyle = "#FFFFFF";
92
- row.strokeStyle = "#000000";
93
- row.overlap_arr = [];
94
- return row;
95
- });
96
- this._ctx.clearRect(0, 0, this.width(), this.height());
97
-
98
- this._drawData = this.enableClustering() ? this.applyClustering(_data) : _data;
99
-
100
- this.draw(this._drawData);
101
- }
102
-
103
- applyClustering(_data: Readonly<CanvasPinRow[]>): Readonly<CanvasPinRow[]> {
104
- const context = this;
105
- this._overlap_count = 0;
106
- const arrow_height = 8;
107
- const pin_h = this.pinHeight();
108
- const pin_w = this.pinWidth();
109
- const half_w = pin_w / 2;
110
- const half_h = pin_h / 2;
111
- const qt = new Quadtree([[0, 0], [this.size().width, this.size().height]], _data, pin_h, pin_w);
112
- this._quadtree_rect_arr = qt.getTreeRects();
113
- switch (this.clusterMode()) {
114
- case "default":
115
- const defData = _data.map(row => {
116
- if (!row.already_flagged) {
117
- const mult = this.searchRectMult();
118
- const left = row[0] - half_w * mult;
119
- const top = row[1] - half_h * mult - arrow_height;
120
- const right = row[0] + half_w * mult;
121
- const bottom = row[1] + half_h * mult - arrow_height;
122
- row.overlap_arr = qt.searchRect(left, top, right, bottom).filter(n => n !== row);
123
- if (row.overlap_arr.length === 0) {
124
- row.already_flagged = false;
125
- }
126
- }
127
- return row;
128
- });
129
- defData.forEach(data_row => {
130
- if (data_row.already_flagged && data_row.overlap_arr.length) {
131
- defData.push(cluster_arr([data_row, ...data_row.overlap_arr]));
132
- }
133
- });
134
- return defData;
135
- case "grid":
136
- const gridData = [..._data];
137
- const grid_cell_w = this.gridCellSize();
138
- const grid_cell_h = this.gridCellSize();
139
- const grid_row_count = Math.ceil(this.size().width / grid_cell_w);
140
- const grid_col_count = Math.ceil(this.size().height / grid_cell_h);
141
- for (let _col = 0; _col < grid_col_count; _col++) {
142
- for (let _row = 0; _row < grid_row_count; _row++) {
143
- const left = grid_cell_w * _row;
144
- const top = grid_cell_h * _col;
145
- const right = grid_cell_w * (_row + 1);
146
- const bottom = grid_cell_h * (_col + 1);
147
- const overlap_arr = qt.searchRect(left, top, right, bottom);
148
- if (overlap_arr.length > 1) {
149
- const x = left + (grid_cell_w / 2);
150
- const y = top + (grid_cell_h / 2);
151
- gridData.push(cluster_arr(overlap_arr, x, y));
152
- }
153
- }
154
- }
155
- return gridData;
156
- }
157
- return _data;
158
-
159
- function cluster_arr(arr: CanvasPinRow[], x?: number, y?: number): CanvasPinRow {
160
- const arr_weight = arr.reduce((a, b) => {
161
- b.already_flagged = true;
162
- return a + b[2];
163
- }, 0);
164
- const _x = typeof x !== "undefined" ? x : context.useAveragePos() ? arr.reduce((a, b) => a + b[0] * b[2], 0) / arr_weight : arr[0][0];
165
- const _y = typeof y !== "undefined" ? y : context.useAveragePos() ? arr.reduce((a, b) => a + b[1] * b[2], 0) / arr_weight : arr[0][1];
166
- return {
167
- 0: _x,
168
- 1: _y,
169
- weight: arr.reduce((a, b) => a + b[2], 0),
170
- is_cluster: true,
171
- already_flagged: false,
172
- fillStyle: "#FFFFFF",
173
- strokeStyle: "#000000",
174
- overlap_arr: []
175
- };
176
- }
177
- }
178
-
179
- drawQuadtree() {
180
- if (!this._quadtree_rect_arr) return;
181
- this._ctx.strokeStyle = "#000000";
182
- this._quadtree_rect_arr.forEach(n => {
183
- this._ctx.strokeRect(n[0], n[1], n[2], n[3]);
184
- });
185
- }
186
- draw(data_arr) {
187
- const context = this;
188
- const ctx = this._ctx;
189
- if (this.showQuadtree()) this.drawQuadtree();
190
- const arrow_height = this.arrowHeight();
191
- const arrow_width = this.arrowWidth();
192
- const pin_h = this.pinHeight();
193
- const pin_w = this.pinWidth();
194
- const weight_bonus: number = this.radiusWeightMult();
195
- let heaviest_cluster = 0;
196
- if (this.useWeightedRadius()) {
197
- data_arr.filter(n => n.is_cluster).forEach(n => {
198
- if (heaviest_cluster < n.weight) {
199
- heaviest_cluster = n.weight;
200
- }
201
- });
202
- }
203
- data_arr
204
- .filter(n => !n.already_flagged)
205
- .sort((a, b) => a[1] > b[1] ? 1 : -1)
206
- .forEach(d => {
207
- if (d.is_cluster || this.allCircles()) {
208
- let _radius = pin_w / 2;
209
- if (this.useWeightedRadius()) {
210
- _radius += pin_w * (weight_bonus * d.weight / heaviest_cluster);
211
- }
212
- drawCirclePin({
213
- icon: d.weight,
214
- left: Math.floor(d[0] - _radius) + 0.5,
215
- top: Math.floor(d[1] - _radius - arrow_height) + 0.5,
216
- radius: _radius,
217
- arrow_height,
218
- arrow_width
219
- });
220
- } else {
221
- drawSquarePin({
222
- fillStyle: d.fillStyle,
223
- strokeStyle: d.strokeStyle,
224
- icon: d.weight,
225
- left: Math.floor(d[0] - (pin_w / 2)) + 0.5,
226
- top: Math.floor(d[1] - pin_h - arrow_height) + 0.5,
227
- width: pin_w,
228
- height: pin_h,
229
- arrow_height,
230
- arrow_width
231
- });
232
- }
233
- });
234
-
235
- function drawCirclePin(p: any) {
236
- p.width = p.radius * 2;
237
- p.height = p.width;
238
- ctx.fillStyle = "#FFFFFF";
239
- ctx.strokeStyle = "#000000";
240
- ctx.beginPath();
241
- ctx.arc(p.left + (p.width / 2), p.top + (p.height / 2), p.radius, 0, 2 * Math.PI);
242
- ctx.closePath();
243
- ctx.fill();
244
- ctx.stroke();
245
- drawPinText(p);
246
- }
247
- function drawSquarePin(p: any) {
248
- ctx.fillStyle = p.fillStyle;
249
- ctx.strokeStyle = "#000000";
250
- ctx.fillRect(p.left, p.top, p.width, p.height);
251
- ctx.strokeRect(p.left, p.top, p.width, p.height);
252
- drawArrow(p);
253
- drawPinText(p);
254
- }
255
- function drawArrow(p: any, is_offset?: boolean) {
256
- const a_x0 = p.left + (p.width / 2) - (p.arrow_width / 2);
257
- const a_x1 = p.left + (p.width / 2);
258
- const a_x2 = p.left + (p.width / 2) + (p.arrow_width / 2);
259
- let a_y0 = p.top + p.height;
260
- let a_y1 = a_y0 + p.arrow_height;
261
- let a_y2 = a_y0;
262
- if (!is_offset) {
263
- ctx.fillStyle = "#FFFFFF";
264
- ctx.strokeStyle = "#000000";
265
- } else {
266
- ctx.fillStyle = "#FFFFFF";
267
- ctx.strokeStyle = "#FFFFFF";
268
- a_y0 -= 2;
269
- a_y1 -= 2;
270
- a_y2 -= 2;
271
- }
272
- ctx.beginPath();
273
- ctx.moveTo(a_x0, a_y0);
274
- ctx.lineTo(a_x1, a_y1);
275
- ctx.lineTo(a_x2, a_y2);
276
- ctx.lineTo(a_x0, a_y0);
277
- ctx.closePath();
278
- ctx.stroke();
279
- ctx.fill();
280
- if (!is_offset) drawArrow(p, true);
281
- }
282
- function drawPinText(p: any) {
283
- ctx.font = `${context.pinFontSize()}px '${context.pinFontFamily()}'`;
284
- const x = p.left + (p.width / 2);
285
- const y = p.top + (p.height / 2);
286
- ctx.textAlign = "center";
287
- ctx.textBaseline = "middle";
288
- ctx.fillStyle = "#000000";
289
- let size_dec = 0;
290
- let txt_w = ctx.measureText(p.icon).width;
291
- while (txt_w > p.width || !context.shrinkFontToPin()) {
292
- size_dec++;
293
- ctx.font = `${context.pinFontSize() - size_dec}px '${context.pinFontFamily()}'`;
294
- txt_w = ctx.measureText(p.icon).width;
295
- }
296
- ctx.fillText(p.icon, x, y);
297
- }
298
- }
299
-
300
- skewedData() {
301
- const context = this;
302
- const retArr = [];
303
- const arr = this.data();
304
- const box = this.size();
305
-
306
- const coordsWidth = this.bottomRightX() - this.topLeftX();
307
- const coordsHeight = this.bottomRightY() - this.topLeftY();
308
-
309
- const pixelValueX = coordsWidth / box.width;
310
- const pixelValueY = coordsHeight / box.height;
311
-
312
- arr.forEach(function (n) {
313
- const left = Math.abs(n[0] - context.topLeftX());
314
- const top = Math.abs(n[1] - context.topLeftY());
315
-
316
- const newX = left / pixelValueX;
317
- const newY = top / pixelValueY;
318
-
319
- retArr.push([newX, newY, n[2]]);
320
- });
321
-
322
- return retArr;
323
- }
324
- }
325
- CanvasPins.prototype._class += " map_CanvasPins";
326
-
327
- export interface CanvasPins {
328
- clusterMode(): any;
329
- clusterMode(_: any): CanvasPins;
330
- gridCellSize(): number;
331
- gridCellSize(_: number): CanvasPins;
332
-
333
- allCircles(): boolean;
334
- allCircles(_: boolean): CanvasPins;
335
- showQuadtree(): boolean;
336
- showQuadtree(_: boolean): CanvasPins;
337
- useAveragePos(): boolean;
338
- useAveragePos(_: boolean): CanvasPins;
339
- useWeightedRadius(): boolean;
340
- useWeightedRadius(_: boolean): CanvasPins;
341
- radiusWeightMult(): number;
342
- radiusWeightMult(_: number): CanvasPins;
343
- shrinkFontToPin(): boolean;
344
- shrinkFontToPin(_: boolean): CanvasPins;
345
- enableClustering(): boolean;
346
- enableClustering(_: boolean): CanvasPins;
347
- searchRectMult(): number;
348
- searchRectMult(_: number): CanvasPins;
349
- bottomRightX(): number;
350
- bottomRightX(_: number): CanvasPins;
351
- bottomRightX_exists(): boolean;
352
- bottomRightY(): number;
353
- bottomRightY(_: number): CanvasPins;
354
- bottomRightY_exists(): boolean;
355
- topLeftX(): number;
356
- topLeftX(_: number): CanvasPins;
357
- topLeftX_exists(): boolean;
358
- topLeftY(): number;
359
- topLeftY(_: number): CanvasPins;
360
- topLeftY_exists(): boolean;
361
-
362
- pinHeight(): number;
363
- pinHeight(_: number): CanvasPins;
364
- pinWidth(): number;
365
- pinWidth(_: number): CanvasPins;
366
- pinFontFamily(): string;
367
- pinFontFamily(_: string): CanvasPins;
368
- pinFontSize(): number;
369
- pinFontSize(_: number): CanvasPins;
370
- arrowHeight(): number;
371
- arrowHeight(_: number): CanvasPins;
372
- arrowWidth(): number;
373
- arrowWidth(_: number): CanvasPins;
374
- }
375
-
376
- CanvasPins.prototype.publish("clusterMode", "default", "set", "clusterMode", ["defualt", "grid"], { tags: ["Basic"], optional: true });
377
- CanvasPins.prototype.publish("gridCellSize", 80, "number", "gridCellSize", null, { tags: ["Basic"], optional: true });
378
-
379
- CanvasPins.prototype.publish("allCircles", false, "boolean", "allCircles", null, { tags: ["Basic"], optional: true });
380
- CanvasPins.prototype.publish("showQuadtree", false, "boolean", "showQuadtree", null, { tags: ["Basic"], optional: true });
381
- CanvasPins.prototype.publish("useAveragePos", false, "boolean", "useAveragePos", null, { tags: ["Basic"], optional: true });
382
- CanvasPins.prototype.publish("shrinkFontToPin", true, "boolean", "shrinkFontToPin", null, { tags: ["Basic"], optional: true });
383
- CanvasPins.prototype.publish("enableClustering", true, "boolean", "enableClustering", null, { tags: ["Basic"], optional: true });
384
- CanvasPins.prototype.publish("useWeightedRadius", false, "boolean", "useWeightedRadius", null, { tags: ["Basic"], optional: true });
385
- CanvasPins.prototype.publish("radiusWeightMult", 0.5, "number", "radiusWeightMult", null, { tags: ["Basic"], optional: true });
386
- CanvasPins.prototype.publish("searchRectMult", 3, "number", "searchRectMult", null, { tags: ["Basic"], optional: true });
387
- CanvasPins.prototype.publish("bottomRightX", null, "number", "Bottom right x-value", null, { tags: ["Basic"], optional: true });
388
- CanvasPins.prototype.publish("bottomRightY", null, "number", "Bottom right y-value", null, { tags: ["Basic"], optional: true });
389
- CanvasPins.prototype.publish("topLeftX", null, "number", "Top left x-value", null, { tags: ["Basic"], optional: true });
390
- CanvasPins.prototype.publish("topLeftY", null, "number", "Top left y-value", null, { tags: ["Basic"], optional: true });
391
-
392
- CanvasPins.prototype.publish("pinHeight", 20, "number", "pinHeight", null, { tags: ["Basic"], optional: true });
393
- CanvasPins.prototype.publish("pinWidth", 20, "number", "pinWidth", null, { tags: ["Basic"], optional: true });
394
- CanvasPins.prototype.publish("pinFontFamily", "Arial", "string", "pinFontFamily", null, { tags: ["Basic"], optional: true });
395
- CanvasPins.prototype.publish("pinFontSize", 14, "number", "pinFontSize", null, { tags: ["Basic"], optional: true });
396
- CanvasPins.prototype.publish("arrowHeight", 8, "number", "arrowHeight", null, { tags: ["Basic"], optional: true });
397
- CanvasPins.prototype.publish("arrowWidth", 8, "number", "arrowWidth", null, { tags: ["Basic"], optional: true });
1
+ import { CanvasWidget } from "@hpcc-js/common";
2
+ import { quadtree as d3quadtree } from "d3-quadtree";
3
+
4
+ export interface CanvasPinRow {
5
+ 0: number;
6
+ 1: number;
7
+ is_cluster: boolean;
8
+ already_flagged: boolean;
9
+ weight: number;
10
+ fillStyle: string;
11
+ strokeStyle: string;
12
+ overlap_arr: CanvasPinRow[];
13
+ }
14
+
15
+ interface Rect {
16
+ left: number;
17
+ right: number;
18
+ top: number;
19
+ bottom: number;
20
+ }
21
+
22
+ class Quadtree {
23
+ protected _pin_h = 0;
24
+ protected _pin_w = 0;
25
+ protected _tree: any;
26
+ constructor(extent: any, raw_data: any, pin_h: number, pin_w: number) {
27
+ this._pin_h = pin_h;
28
+ this._pin_w = pin_w;
29
+ this._tree = d3quadtree()
30
+ .extent(extent)
31
+ .addAll(raw_data);
32
+ }
33
+
34
+ searchRect(left: number, top: number, right: number, bottom: number): CanvasPinRow[] {
35
+ const ret: any = [];
36
+ this._tree.visit((node: any, x1: number, y1: number, x2: number, y2: number) => {
37
+ let next_exists = false;
38
+ do {
39
+ if (!node.length) {
40
+ if (node.data && !node.data.already_flagged) {
41
+ const is_overlapping = overlaps({ left, right, top, bottom }, node.data);
42
+ if (is_overlapping) {
43
+ node.data.already_flagged = true;
44
+ ret.push(node.data);
45
+ }
46
+ }
47
+ }
48
+ next_exists = node = node.next;
49
+ } while (next_exists);
50
+ return x1 >= right || y1 >= bottom || x2 < left || y2 < top;
51
+ });
52
+ return ret;
53
+ function overlaps(r1: Rect, point: CanvasPinRow) {
54
+ return (point[0] < r1.right || point[0] > r1.left) && (point[1] < r1.bottom || point[1] > r1.top);
55
+ }
56
+ }
57
+ getTreeRects(): any {
58
+ const ret = [];
59
+ this._tree.visit((node: any, x1: number, y1: number, x2: number, y2: number) => {
60
+ ret.push([x1, y1, x2 - x1, y2 - y1]);
61
+ });
62
+ return ret;
63
+ }
64
+ }
65
+
66
+ export class CanvasPins extends CanvasWidget {
67
+ _quadtree_rect_arr;
68
+ _drawData;
69
+ _overlap_count = 0;
70
+ _sub_overlap_count = 0;
71
+
72
+ constructor() {
73
+ super();
74
+ }
75
+
76
+ enter(domNode, element) {
77
+ super.enter.apply(this, arguments);
78
+ this.resize(this._size);
79
+ }
80
+
81
+ update(domNode, element) {
82
+ super.update.apply(this, arguments);
83
+ this._ctx = this.element().node().getContext("2d");
84
+ const needs_data_skew = this.topLeftX_exists() && this.topLeftY_exists() && this.bottomRightX_exists() && this.bottomRightY_exists();
85
+ const _data = (needs_data_skew ? this.skewedData() : this.data())
86
+ .map(row => [...row])
87
+ .map(row => {
88
+ row.is_cluster = false;
89
+ row.already_flagged = false;
90
+ row.weight = row[2];
91
+ row.fillStyle = "#FFFFFF";
92
+ row.strokeStyle = "#000000";
93
+ row.overlap_arr = [];
94
+ return row;
95
+ });
96
+ this._ctx.clearRect(0, 0, this.width(), this.height());
97
+
98
+ this._drawData = this.enableClustering() ? this.applyClustering(_data) : _data;
99
+
100
+ this.draw(this._drawData);
101
+ }
102
+
103
+ applyClustering(_data: Readonly<CanvasPinRow[]>): Readonly<CanvasPinRow[]> {
104
+ const context = this;
105
+ this._overlap_count = 0;
106
+ const arrow_height = 8;
107
+ const pin_h = this.pinHeight();
108
+ const pin_w = this.pinWidth();
109
+ const half_w = pin_w / 2;
110
+ const half_h = pin_h / 2;
111
+ const qt = new Quadtree([[0, 0], [this.size().width, this.size().height]], _data, pin_h, pin_w);
112
+ this._quadtree_rect_arr = qt.getTreeRects();
113
+ switch (this.clusterMode()) {
114
+ case "default":
115
+ const defData = _data.map(row => {
116
+ if (!row.already_flagged) {
117
+ const mult = this.searchRectMult();
118
+ const left = row[0] - half_w * mult;
119
+ const top = row[1] - half_h * mult - arrow_height;
120
+ const right = row[0] + half_w * mult;
121
+ const bottom = row[1] + half_h * mult - arrow_height;
122
+ row.overlap_arr = qt.searchRect(left, top, right, bottom).filter(n => n !== row);
123
+ if (row.overlap_arr.length === 0) {
124
+ row.already_flagged = false;
125
+ }
126
+ }
127
+ return row;
128
+ });
129
+ defData.forEach(data_row => {
130
+ if (data_row.already_flagged && data_row.overlap_arr.length) {
131
+ defData.push(cluster_arr([data_row, ...data_row.overlap_arr]));
132
+ }
133
+ });
134
+ return defData;
135
+ case "grid":
136
+ const gridData = [..._data];
137
+ const grid_cell_w = this.gridCellSize();
138
+ const grid_cell_h = this.gridCellSize();
139
+ const grid_row_count = Math.ceil(this.size().width / grid_cell_w);
140
+ const grid_col_count = Math.ceil(this.size().height / grid_cell_h);
141
+ for (let _col = 0; _col < grid_col_count; _col++) {
142
+ for (let _row = 0; _row < grid_row_count; _row++) {
143
+ const left = grid_cell_w * _row;
144
+ const top = grid_cell_h * _col;
145
+ const right = grid_cell_w * (_row + 1);
146
+ const bottom = grid_cell_h * (_col + 1);
147
+ const overlap_arr = qt.searchRect(left, top, right, bottom);
148
+ if (overlap_arr.length > 1) {
149
+ const x = left + (grid_cell_w / 2);
150
+ const y = top + (grid_cell_h / 2);
151
+ gridData.push(cluster_arr(overlap_arr, x, y));
152
+ }
153
+ }
154
+ }
155
+ return gridData;
156
+ }
157
+ return _data;
158
+
159
+ function cluster_arr(arr: CanvasPinRow[], x?: number, y?: number): CanvasPinRow {
160
+ const arr_weight = arr.reduce((a, b) => {
161
+ b.already_flagged = true;
162
+ return a + b[2];
163
+ }, 0);
164
+ const _x = typeof x !== "undefined" ? x : context.useAveragePos() ? arr.reduce((a, b) => a + b[0] * b[2], 0) / arr_weight : arr[0][0];
165
+ const _y = typeof y !== "undefined" ? y : context.useAveragePos() ? arr.reduce((a, b) => a + b[1] * b[2], 0) / arr_weight : arr[0][1];
166
+ return {
167
+ 0: _x,
168
+ 1: _y,
169
+ weight: arr.reduce((a, b) => a + b[2], 0),
170
+ is_cluster: true,
171
+ already_flagged: false,
172
+ fillStyle: "#FFFFFF",
173
+ strokeStyle: "#000000",
174
+ overlap_arr: []
175
+ };
176
+ }
177
+ }
178
+
179
+ drawQuadtree() {
180
+ if (!this._quadtree_rect_arr) return;
181
+ this._ctx.strokeStyle = "#000000";
182
+ this._quadtree_rect_arr.forEach(n => {
183
+ this._ctx.strokeRect(n[0], n[1], n[2], n[3]);
184
+ });
185
+ }
186
+ draw(data_arr) {
187
+ const context = this;
188
+ const ctx = this._ctx;
189
+ if (this.showQuadtree()) this.drawQuadtree();
190
+ const arrow_height = this.arrowHeight();
191
+ const arrow_width = this.arrowWidth();
192
+ const pin_h = this.pinHeight();
193
+ const pin_w = this.pinWidth();
194
+ const weight_bonus: number = this.radiusWeightMult();
195
+ let heaviest_cluster = 0;
196
+ if (this.useWeightedRadius()) {
197
+ data_arr.filter(n => n.is_cluster).forEach(n => {
198
+ if (heaviest_cluster < n.weight) {
199
+ heaviest_cluster = n.weight;
200
+ }
201
+ });
202
+ }
203
+ data_arr
204
+ .filter(n => !n.already_flagged)
205
+ .sort((a, b) => a[1] > b[1] ? 1 : -1)
206
+ .forEach(d => {
207
+ if (d.is_cluster || this.allCircles()) {
208
+ let _radius = pin_w / 2;
209
+ if (this.useWeightedRadius()) {
210
+ _radius += pin_w * (weight_bonus * d.weight / heaviest_cluster);
211
+ }
212
+ drawCirclePin({
213
+ icon: d.weight,
214
+ left: Math.floor(d[0] - _radius) + 0.5,
215
+ top: Math.floor(d[1] - _radius - arrow_height) + 0.5,
216
+ radius: _radius,
217
+ arrow_height,
218
+ arrow_width
219
+ });
220
+ } else {
221
+ drawSquarePin({
222
+ fillStyle: d.fillStyle,
223
+ strokeStyle: d.strokeStyle,
224
+ icon: d.weight,
225
+ left: Math.floor(d[0] - (pin_w / 2)) + 0.5,
226
+ top: Math.floor(d[1] - pin_h - arrow_height) + 0.5,
227
+ width: pin_w,
228
+ height: pin_h,
229
+ arrow_height,
230
+ arrow_width
231
+ });
232
+ }
233
+ });
234
+
235
+ function drawCirclePin(p: any) {
236
+ p.width = p.radius * 2;
237
+ p.height = p.width;
238
+ ctx.fillStyle = "#FFFFFF";
239
+ ctx.strokeStyle = "#000000";
240
+ ctx.beginPath();
241
+ ctx.arc(p.left + (p.width / 2), p.top + (p.height / 2), p.radius, 0, 2 * Math.PI);
242
+ ctx.closePath();
243
+ ctx.fill();
244
+ ctx.stroke();
245
+ drawPinText(p);
246
+ }
247
+ function drawSquarePin(p: any) {
248
+ ctx.fillStyle = p.fillStyle;
249
+ ctx.strokeStyle = "#000000";
250
+ ctx.fillRect(p.left, p.top, p.width, p.height);
251
+ ctx.strokeRect(p.left, p.top, p.width, p.height);
252
+ drawArrow(p);
253
+ drawPinText(p);
254
+ }
255
+ function drawArrow(p: any, is_offset?: boolean) {
256
+ const a_x0 = p.left + (p.width / 2) - (p.arrow_width / 2);
257
+ const a_x1 = p.left + (p.width / 2);
258
+ const a_x2 = p.left + (p.width / 2) + (p.arrow_width / 2);
259
+ let a_y0 = p.top + p.height;
260
+ let a_y1 = a_y0 + p.arrow_height;
261
+ let a_y2 = a_y0;
262
+ if (!is_offset) {
263
+ ctx.fillStyle = "#FFFFFF";
264
+ ctx.strokeStyle = "#000000";
265
+ } else {
266
+ ctx.fillStyle = "#FFFFFF";
267
+ ctx.strokeStyle = "#FFFFFF";
268
+ a_y0 -= 2;
269
+ a_y1 -= 2;
270
+ a_y2 -= 2;
271
+ }
272
+ ctx.beginPath();
273
+ ctx.moveTo(a_x0, a_y0);
274
+ ctx.lineTo(a_x1, a_y1);
275
+ ctx.lineTo(a_x2, a_y2);
276
+ ctx.lineTo(a_x0, a_y0);
277
+ ctx.closePath();
278
+ ctx.stroke();
279
+ ctx.fill();
280
+ if (!is_offset) drawArrow(p, true);
281
+ }
282
+ function drawPinText(p: any) {
283
+ ctx.font = `${context.pinFontSize()}px '${context.pinFontFamily()}'`;
284
+ const x = p.left + (p.width / 2);
285
+ const y = p.top + (p.height / 2);
286
+ ctx.textAlign = "center";
287
+ ctx.textBaseline = "middle";
288
+ ctx.fillStyle = "#000000";
289
+ let size_dec = 0;
290
+ let txt_w = ctx.measureText(p.icon).width;
291
+ while (txt_w > p.width || !context.shrinkFontToPin()) {
292
+ size_dec++;
293
+ ctx.font = `${context.pinFontSize() - size_dec}px '${context.pinFontFamily()}'`;
294
+ txt_w = ctx.measureText(p.icon).width;
295
+ }
296
+ ctx.fillText(p.icon, x, y);
297
+ }
298
+ }
299
+
300
+ skewedData() {
301
+ const context = this;
302
+ const retArr = [];
303
+ const arr = this.data();
304
+ const box = this.size();
305
+
306
+ const coordsWidth = this.bottomRightX() - this.topLeftX();
307
+ const coordsHeight = this.bottomRightY() - this.topLeftY();
308
+
309
+ const pixelValueX = coordsWidth / box.width;
310
+ const pixelValueY = coordsHeight / box.height;
311
+
312
+ arr.forEach(function (n) {
313
+ const left = Math.abs(n[0] - context.topLeftX());
314
+ const top = Math.abs(n[1] - context.topLeftY());
315
+
316
+ const newX = left / pixelValueX;
317
+ const newY = top / pixelValueY;
318
+
319
+ retArr.push([newX, newY, n[2]]);
320
+ });
321
+
322
+ return retArr;
323
+ }
324
+ }
325
+ CanvasPins.prototype._class += " map_CanvasPins";
326
+
327
+ export interface CanvasPins {
328
+ clusterMode(): any;
329
+ clusterMode(_: any): CanvasPins;
330
+ gridCellSize(): number;
331
+ gridCellSize(_: number): CanvasPins;
332
+
333
+ allCircles(): boolean;
334
+ allCircles(_: boolean): CanvasPins;
335
+ showQuadtree(): boolean;
336
+ showQuadtree(_: boolean): CanvasPins;
337
+ useAveragePos(): boolean;
338
+ useAveragePos(_: boolean): CanvasPins;
339
+ useWeightedRadius(): boolean;
340
+ useWeightedRadius(_: boolean): CanvasPins;
341
+ radiusWeightMult(): number;
342
+ radiusWeightMult(_: number): CanvasPins;
343
+ shrinkFontToPin(): boolean;
344
+ shrinkFontToPin(_: boolean): CanvasPins;
345
+ enableClustering(): boolean;
346
+ enableClustering(_: boolean): CanvasPins;
347
+ searchRectMult(): number;
348
+ searchRectMult(_: number): CanvasPins;
349
+ bottomRightX(): number;
350
+ bottomRightX(_: number): CanvasPins;
351
+ bottomRightX_exists(): boolean;
352
+ bottomRightY(): number;
353
+ bottomRightY(_: number): CanvasPins;
354
+ bottomRightY_exists(): boolean;
355
+ topLeftX(): number;
356
+ topLeftX(_: number): CanvasPins;
357
+ topLeftX_exists(): boolean;
358
+ topLeftY(): number;
359
+ topLeftY(_: number): CanvasPins;
360
+ topLeftY_exists(): boolean;
361
+
362
+ pinHeight(): number;
363
+ pinHeight(_: number): CanvasPins;
364
+ pinWidth(): number;
365
+ pinWidth(_: number): CanvasPins;
366
+ pinFontFamily(): string;
367
+ pinFontFamily(_: string): CanvasPins;
368
+ pinFontSize(): number;
369
+ pinFontSize(_: number): CanvasPins;
370
+ arrowHeight(): number;
371
+ arrowHeight(_: number): CanvasPins;
372
+ arrowWidth(): number;
373
+ arrowWidth(_: number): CanvasPins;
374
+ }
375
+
376
+ CanvasPins.prototype.publish("clusterMode", "default", "set", "clusterMode", ["defualt", "grid"], { tags: ["Basic"], optional: true });
377
+ CanvasPins.prototype.publish("gridCellSize", 80, "number", "gridCellSize", null, { tags: ["Basic"], optional: true });
378
+
379
+ CanvasPins.prototype.publish("allCircles", false, "boolean", "allCircles", null, { tags: ["Basic"], optional: true });
380
+ CanvasPins.prototype.publish("showQuadtree", false, "boolean", "showQuadtree", null, { tags: ["Basic"], optional: true });
381
+ CanvasPins.prototype.publish("useAveragePos", false, "boolean", "useAveragePos", null, { tags: ["Basic"], optional: true });
382
+ CanvasPins.prototype.publish("shrinkFontToPin", true, "boolean", "shrinkFontToPin", null, { tags: ["Basic"], optional: true });
383
+ CanvasPins.prototype.publish("enableClustering", true, "boolean", "enableClustering", null, { tags: ["Basic"], optional: true });
384
+ CanvasPins.prototype.publish("useWeightedRadius", false, "boolean", "useWeightedRadius", null, { tags: ["Basic"], optional: true });
385
+ CanvasPins.prototype.publish("radiusWeightMult", 0.5, "number", "radiusWeightMult", null, { tags: ["Basic"], optional: true });
386
+ CanvasPins.prototype.publish("searchRectMult", 3, "number", "searchRectMult", null, { tags: ["Basic"], optional: true });
387
+ CanvasPins.prototype.publish("bottomRightX", null, "number", "Bottom right x-value", null, { tags: ["Basic"], optional: true });
388
+ CanvasPins.prototype.publish("bottomRightY", null, "number", "Bottom right y-value", null, { tags: ["Basic"], optional: true });
389
+ CanvasPins.prototype.publish("topLeftX", null, "number", "Top left x-value", null, { tags: ["Basic"], optional: true });
390
+ CanvasPins.prototype.publish("topLeftY", null, "number", "Top left y-value", null, { tags: ["Basic"], optional: true });
391
+
392
+ CanvasPins.prototype.publish("pinHeight", 20, "number", "pinHeight", null, { tags: ["Basic"], optional: true });
393
+ CanvasPins.prototype.publish("pinWidth", 20, "number", "pinWidth", null, { tags: ["Basic"], optional: true });
394
+ CanvasPins.prototype.publish("pinFontFamily", "Arial", "string", "pinFontFamily", null, { tags: ["Basic"], optional: true });
395
+ CanvasPins.prototype.publish("pinFontSize", 14, "number", "pinFontSize", null, { tags: ["Basic"], optional: true });
396
+ CanvasPins.prototype.publish("arrowHeight", 8, "number", "arrowHeight", null, { tags: ["Basic"], optional: true });
397
+ CanvasPins.prototype.publish("arrowWidth", 8, "number", "arrowWidth", null, { tags: ["Basic"], optional: true });