@hpcc-js/map 3.4.10 → 3.4.11

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 +7 -7
  15. package/src/CanvasPinLayer.ts +99 -99
  16. package/src/CanvasPins.ts +397 -397
  17. package/src/Choropleth.css +27 -27
  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 +16 -16
  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 +15 -15
  33. package/src/GeoHash.ts +139 -139
  34. package/src/Graph.css +10 -10
  35. package/src/Graph.ts +98 -98
  36. package/src/Graticule.css +13 -13
  37. package/src/Graticule.ts +97 -97
  38. package/src/Heat.css +2 -2
  39. package/src/Heat.ts +87 -87
  40. package/src/IChoropleth.ts +8 -8
  41. package/src/Layer.ts +99 -99
  42. package/src/Layered.css +19 -19
  43. package/src/Layered.ts +206 -206
  44. package/src/Lines.css +9 -9
  45. package/src/Lines.ts +78 -78
  46. package/src/OpenStreet.css +15 -15
  47. package/src/OpenStreet.ts +126 -126
  48. package/src/Pins.css +18 -18
  49. package/src/Pins.ts +350 -350
  50. package/src/Projection.ts +42 -42
  51. package/src/TestHeatMap.ts +8 -8
  52. package/src/TopoJSONChoropleth.ts +125 -125
  53. package/src/Utility.ts +484 -484
  54. package/src/__package__.ts +3 -3
  55. package/src/index.ts +33 -33
  56. package/src/leaflet/AlbersPR.ts +48 -48
  57. package/src/leaflet/Blank.ts +9 -9
  58. package/src/leaflet/Circles.ts +139 -139
  59. package/src/leaflet/ClusterCircles.css +26 -26
  60. package/src/leaflet/ClusterCircles.ts +88 -88
  61. package/src/leaflet/Countries.ts +43 -43
  62. package/src/leaflet/DrawLayer.ts +167 -167
  63. package/src/leaflet/FeatureLayer.ts +138 -138
  64. package/src/leaflet/GMap.ts +44 -44
  65. package/src/leaflet/HeatLayer.ts +77 -77
  66. package/src/leaflet/Icons.ts +60 -60
  67. package/src/leaflet/Leaflet.css +3 -3
  68. package/src/leaflet/Leaflet.ts +239 -239
  69. package/src/leaflet/MapBox.ts +35 -35
  70. package/src/leaflet/Markers.ts +109 -109
  71. package/src/leaflet/OpenStreet.ts +27 -27
  72. package/src/leaflet/Path.ts +138 -138
  73. package/src/leaflet/Pins.ts +73 -73
  74. package/src/leaflet/Polygons.ts +113 -113
  75. package/src/leaflet/Region.ts +138 -138
  76. package/src/leaflet/Text.ts +99 -99
  77. package/src/leaflet/TileLayer.ts +81 -81
  78. package/src/leaflet/TopoJSON.ts +146 -146
  79. package/src/leaflet/US.ts +15 -15
  80. package/src/leaflet/USCounties.ts +43 -43
  81. package/src/leaflet/USStates.ts +41 -41
  82. package/src/leaflet/World.css +3 -3
  83. package/src/leaflet/World.ts +172 -172
  84. package/src/leaflet/index.ts +18 -18
  85. package/src/leaflet/leaflet-shim.ts +18 -18
  86. package/src/leaflet/plugins/BeautifyIcon.css +44 -44
  87. package/src/leaflet/plugins/BeautifyIcon.ts +190 -190
  88. package/src/leaflet/plugins/BeutifyIcon.licence +20 -20
  89. package/src/leaflet/plugins/D3SvgOverlay.css +3 -3
  90. package/src/leaflet/plugins/D3SvgOverlay.licence +20 -20
  91. package/src/leaflet/plugins/D3SvgOverlay.ts +175 -175
  92. package/src/leaflet/plugins/HeatLayer.license +21 -21
  93. package/src/leaflet/plugins/HeatLayer.ts +224 -224
  94. package/src/leaflet/plugins/Leaflet.GoogleMutant.ts +424 -424
  95. package/src/leaflet/plugins/lru_map.ts +352 -352
  96. package/src/test.ts +114 -114
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 });