@guardian/interactive-component-library 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/molecules/canvas-map/lib/Feature.d.ts +4 -1
- package/dist/components/molecules/canvas-map/lib/Feature.js +3 -0
- package/dist/components/molecules/canvas-map/lib/layers/TextLayer.d.ts +49 -15
- package/dist/components/molecules/canvas-map/lib/layers/TextLayer.js +4 -1
- package/dist/components/molecules/canvas-map/lib/layers/VectorLayer.d.ts +36 -10
- package/dist/components/molecules/canvas-map/lib/layers/VectorLayer.js +6 -3
- package/dist/components/molecules/canvas-map/lib/renderers/MapRenderer.d.ts +14 -0
- package/dist/components/molecules/canvas-map/lib/renderers/MapRenderer.js +52 -8
- package/dist/components/molecules/canvas-map/lib/renderers/TextLayerRenderer.d.ts +50 -5
- package/dist/components/molecules/canvas-map/lib/renderers/TextLayerRenderer.js +171 -24
- package/dist/components/molecules/canvas-map/lib/renderers/VectorLayerRenderer.d.ts +13 -6
- package/dist/components/molecules/canvas-map/lib/renderers/VectorLayerRenderer.js +12 -42
- package/dist/components/molecules/canvas-map/lib/sources/VectorSource.d.ts +24 -6
- package/dist/components/molecules/canvas-map/lib/sources/VectorSource.js +18 -0
- package/dist/components/molecules/canvas-map/lib/styles/Style.d.ts +40 -11
- package/dist/components/molecules/canvas-map/lib/styles/Style.js +7 -0
- package/dist/components/molecules/canvas-map/lib/styles/Text.d.ts +184 -11
- package/dist/components/molecules/canvas-map/lib/styles/Text.js +18 -3
- package/dist/components/molecules/result-summary/index.d.ts +2 -1
- package/dist/components/molecules/result-summary/index.js +30 -5
- package/dist/components/molecules/result-summary/style.module.css.js +12 -3
- package/dist/style.css +21 -2
- package/package.json +1 -1
|
@@ -32,7 +32,10 @@ export class Feature {
|
|
|
32
32
|
*/
|
|
33
33
|
getExtent(): import('./util').Extent;
|
|
34
34
|
getProjectedGeometries(projection: any): any;
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* @returns {import("./styles/Style").StyleFunction}
|
|
37
|
+
*/
|
|
38
|
+
getStyleFunction(): import('./styles/Style').StyleFunction;
|
|
36
39
|
containsCoordinate(coordinate: any): boolean;
|
|
37
40
|
clone(): Feature;
|
|
38
41
|
/**
|
|
@@ -45,6 +45,9 @@ class Feature {
|
|
|
45
45
|
getProjectedGeometries(projection) {
|
|
46
46
|
return this._getProjectedGeometries(projection, projection.revision);
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* @returns {import("./styles/Style").StyleFunction}
|
|
50
|
+
*/
|
|
48
51
|
getStyleFunction() {
|
|
49
52
|
const style = this.style;
|
|
50
53
|
if (!style) return null;
|
|
@@ -28,25 +28,59 @@ export class TextLayer {
|
|
|
28
28
|
declutter?: boolean;
|
|
29
29
|
drawCollisionBoxes?: boolean;
|
|
30
30
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
/**
|
|
32
|
+
* @type {VectorSource}
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
public source: VectorSource;
|
|
36
|
+
/**
|
|
37
|
+
* @type {Style|import("../styles").StyleFunction}
|
|
38
|
+
*/
|
|
39
|
+
_style: Style | import('../styles').StyleFunction;
|
|
40
|
+
/**
|
|
41
|
+
* @type {number}
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
public minZoom: number;
|
|
45
|
+
/**
|
|
46
|
+
* @type {number}
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
public opacity: number;
|
|
50
|
+
/**
|
|
51
|
+
* @type {boolean}
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
public declutter: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* @type {boolean}
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
public drawCollisionBoxes: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* @type {TextLayerRenderer}
|
|
62
|
+
* @public
|
|
63
|
+
*/
|
|
64
|
+
public renderer: TextLayerRenderer;
|
|
65
|
+
/**
|
|
66
|
+
* @type {Dispatcher}
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
public dispatcher: Dispatcher;
|
|
39
70
|
tearDown(): void;
|
|
40
|
-
set style(style: Style | (
|
|
41
|
-
get style(): Style | (
|
|
42
|
-
getExtent():
|
|
43
|
-
_extent:
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
set style(style: Style | import('../styles').StyleFunction);
|
|
72
|
+
get style(): Style | import('../styles').StyleFunction;
|
|
73
|
+
getExtent(): import('../Feature').Feature;
|
|
74
|
+
_extent: import('../Feature').Feature;
|
|
75
|
+
/**
|
|
76
|
+
* @returns {import("../styles/Style").StyleFunction}
|
|
77
|
+
*/
|
|
78
|
+
getStyleFunction(): import('../styles/Style').StyleFunction;
|
|
79
|
+
renderFrame(frameState: any, targetElement: any): HTMLDivElement;
|
|
46
80
|
}
|
|
47
81
|
export namespace TextLayer {
|
|
48
82
|
/** @param {TextLayerComponentProps} props */
|
|
49
|
-
function Component({ features: featureCollection, style, minZoom, opacity, declutter, drawCollisionBoxes, }: TextLayerComponentProps):
|
|
83
|
+
function Component({ features: featureCollection, style, minZoom, opacity, declutter, drawCollisionBoxes, }: TextLayerComponentProps): any;
|
|
50
84
|
namespace Component {
|
|
51
85
|
let displayName: string;
|
|
52
86
|
}
|
|
@@ -45,7 +45,7 @@ class TextLayer {
|
|
|
45
45
|
useEffect(() => {
|
|
46
46
|
layer.style = style;
|
|
47
47
|
}, [style]);
|
|
48
|
-
return
|
|
48
|
+
return null;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* @param {import("../Feature").Feature[]} features
|
|
@@ -107,6 +107,9 @@ class TextLayer {
|
|
|
107
107
|
this._extent = extent;
|
|
108
108
|
return extent;
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* @returns {import("../styles/Style").StyleFunction}
|
|
112
|
+
*/
|
|
110
113
|
getStyleFunction() {
|
|
111
114
|
const style = this.style;
|
|
112
115
|
if (typeof style === "function") return style;
|
|
@@ -25,22 +25,48 @@ export class VectorLayer {
|
|
|
25
25
|
opacity?: number;
|
|
26
26
|
hitDetectionEnabled?: boolean;
|
|
27
27
|
});
|
|
28
|
-
dispatcher: Dispatcher;
|
|
29
|
-
renderer: VectorLayerRenderer;
|
|
30
28
|
set source(source: any);
|
|
31
29
|
get source(): any;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
/**
|
|
31
|
+
* @type {Dispatcher}
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
public dispatcher: Dispatcher;
|
|
35
|
+
/**
|
|
36
|
+
* @type {VectorLayerRenderer}
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
public renderer: VectorLayerRenderer;
|
|
40
|
+
/**
|
|
41
|
+
* @type {number}
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
public minZoom: number;
|
|
45
|
+
/**
|
|
46
|
+
* @type {number}
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
public opacity: number;
|
|
50
|
+
/**
|
|
51
|
+
* @type {boolean}
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
public hitDetectionEnabled: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* @type {Style|import("../styles").StyleFunction}
|
|
57
|
+
*/
|
|
58
|
+
_style: Style | import('../styles').StyleFunction;
|
|
36
59
|
_source: any;
|
|
37
60
|
_extent: any;
|
|
38
61
|
setRawProjection(projection: any): void;
|
|
39
62
|
projection: any;
|
|
40
63
|
tearDown(): void;
|
|
41
|
-
set style(style: Style | (
|
|
42
|
-
get style(): Style | (
|
|
43
|
-
|
|
64
|
+
set style(style: Style | import('../styles').StyleFunction);
|
|
65
|
+
get style(): Style | import('../styles').StyleFunction;
|
|
66
|
+
/**
|
|
67
|
+
* @returns {import("../styles/Style").StyleFunction}
|
|
68
|
+
*/
|
|
69
|
+
getStyleFunction(): import('../styles/Style').StyleFunction;
|
|
44
70
|
/**
|
|
45
71
|
* Get the extent of the features in the layer.
|
|
46
72
|
* @returns {import("../util").Extent | null} The extent of the features in the layer, or null if the layer is empty.
|
|
@@ -51,7 +77,7 @@ export class VectorLayer {
|
|
|
51
77
|
}
|
|
52
78
|
export namespace VectorLayer {
|
|
53
79
|
/** @param {VectorLayerComponentProps} props */
|
|
54
|
-
function Component({ features: featureCollection, style, minZoom, opacity, hitDetectionEnabled, }: VectorLayerComponentProps):
|
|
80
|
+
function Component({ features: featureCollection, style, minZoom, opacity, hitDetectionEnabled, }: VectorLayerComponentProps): any;
|
|
55
81
|
namespace Component {
|
|
56
82
|
let displayName: string;
|
|
57
83
|
}
|
|
@@ -43,7 +43,7 @@ class VectorLayer {
|
|
|
43
43
|
useEffect(() => {
|
|
44
44
|
layer.style = style;
|
|
45
45
|
}, [style]);
|
|
46
|
-
return
|
|
46
|
+
return null;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* @param {import("../Feature").Feature[]} features
|
|
@@ -68,13 +68,13 @@ class VectorLayer {
|
|
|
68
68
|
opacity = 1,
|
|
69
69
|
hitDetectionEnabled = true
|
|
70
70
|
}) {
|
|
71
|
+
this.source = source;
|
|
71
72
|
this.dispatcher = new Dispatcher(this);
|
|
72
73
|
this.renderer = new VectorLayerRenderer(this);
|
|
73
|
-
this.source = source;
|
|
74
|
-
this._style = style;
|
|
75
74
|
this.minZoom = minZoom;
|
|
76
75
|
this.opacity = opacity;
|
|
77
76
|
this.hitDetectionEnabled = hitDetectionEnabled;
|
|
77
|
+
this._style = style;
|
|
78
78
|
}
|
|
79
79
|
get source() {
|
|
80
80
|
return this._source;
|
|
@@ -106,6 +106,9 @@ class VectorLayer {
|
|
|
106
106
|
this._style = style;
|
|
107
107
|
this.dispatcher.dispatch(MapEvent.CHANGE);
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* @returns {import("../styles/Style").StyleFunction}
|
|
111
|
+
*/
|
|
109
112
|
getStyleFunction() {
|
|
110
113
|
const style = this.style;
|
|
111
114
|
if (typeof style === "function") return style;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} CanvasSingleton
|
|
3
|
+
* @property {() => CanvasRenderingContext2D} getContext2d
|
|
4
|
+
* @property {() => HTMLDivElement} getContainer
|
|
5
|
+
* @property {() => boolean} isInitialised
|
|
6
|
+
*
|
|
7
|
+
* @typedef {Object} FrameState
|
|
8
|
+
*/
|
|
1
9
|
export class MapRenderer {
|
|
2
10
|
constructor(map: any);
|
|
3
11
|
map: any;
|
|
4
12
|
_element: HTMLDivElement;
|
|
5
13
|
renderFrame(frameState: any): void;
|
|
6
14
|
}
|
|
15
|
+
export type CanvasSingleton = {
|
|
16
|
+
getContext2d: () => CanvasRenderingContext2D;
|
|
17
|
+
getContainer: () => HTMLDivElement;
|
|
18
|
+
isInitialised: () => boolean;
|
|
19
|
+
};
|
|
20
|
+
export type FrameState = any;
|
|
@@ -14,25 +14,24 @@ class MapRenderer {
|
|
|
14
14
|
container.insertBefore(this._element, container.firstChild || null);
|
|
15
15
|
}
|
|
16
16
|
renderFrame(frameState) {
|
|
17
|
-
const { zoomLevel, projection } = frameState.viewState;
|
|
17
|
+
const { zoomLevel, projection, sizeInPixels } = frameState.viewState;
|
|
18
18
|
const layers = this.map.layers;
|
|
19
19
|
const mapElements = [];
|
|
20
|
-
let previousElement = null;
|
|
21
20
|
const visibleLayers = layers.filter((layer) => {
|
|
22
21
|
return zoomLevel > (layer.minZoom || 0);
|
|
23
22
|
});
|
|
23
|
+
const canvasSingleton = makeCanvasSingleton(sizeInPixels);
|
|
24
24
|
const renderLayer = (layer, declutterTree2) => {
|
|
25
25
|
const viewState = frameState.viewState;
|
|
26
26
|
if (layer.projection) {
|
|
27
27
|
viewState.projection = layer.projection;
|
|
28
28
|
}
|
|
29
|
-
const
|
|
29
|
+
const newContainer = layer.renderFrame(
|
|
30
30
|
{ ...frameState, viewState, declutterTree: declutterTree2 },
|
|
31
|
-
|
|
31
|
+
canvasSingleton
|
|
32
32
|
);
|
|
33
|
-
if (
|
|
34
|
-
mapElements.push(
|
|
35
|
-
previousElement = element;
|
|
33
|
+
if (newContainer) {
|
|
34
|
+
mapElements.push(newContainer);
|
|
36
35
|
}
|
|
37
36
|
viewState.projection = projection;
|
|
38
37
|
};
|
|
@@ -44,9 +43,54 @@ class MapRenderer {
|
|
|
44
43
|
renderLayer(layer);
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
|
-
|
|
46
|
+
if (canvasSingleton.isInitialised()) {
|
|
47
|
+
replaceChildren(this._element, [
|
|
48
|
+
canvasSingleton.getContainer(),
|
|
49
|
+
...mapElements
|
|
50
|
+
]);
|
|
51
|
+
} else {
|
|
52
|
+
replaceChildren(this._element, mapElements);
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
}
|
|
56
|
+
function makeCanvasSingleton(sizeInPixels) {
|
|
57
|
+
let canvasLayer;
|
|
58
|
+
let canvasContext2d;
|
|
59
|
+
return {
|
|
60
|
+
getContext2d: () => {
|
|
61
|
+
if (!canvasLayer) {
|
|
62
|
+
canvasLayer = createCanvasMapLayer(sizeInPixels);
|
|
63
|
+
canvasContext2d = canvasLayer.firstElementChild.getContext("2d");
|
|
64
|
+
}
|
|
65
|
+
return canvasContext2d;
|
|
66
|
+
},
|
|
67
|
+
getContainer: () => {
|
|
68
|
+
if (!canvasLayer) {
|
|
69
|
+
canvasLayer = createCanvasMapLayer(sizeInPixels);
|
|
70
|
+
canvasContext2d = canvasLayer.firstElementChild.getContext("2d");
|
|
71
|
+
}
|
|
72
|
+
return canvasLayer;
|
|
73
|
+
},
|
|
74
|
+
isInitialised: () => !!canvasLayer
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function createCanvasMapLayer(sizeInPixels) {
|
|
78
|
+
const container = document.createElement("div");
|
|
79
|
+
container.className = "gv-map-layer";
|
|
80
|
+
let style = container.style;
|
|
81
|
+
style.position = "absolute";
|
|
82
|
+
style.width = "100%";
|
|
83
|
+
style.height = "100%";
|
|
84
|
+
const canvas = document.createElement("canvas");
|
|
85
|
+
style = canvas.style;
|
|
86
|
+
style.position = "absolute";
|
|
87
|
+
style.width = "100%";
|
|
88
|
+
style.height = "100%";
|
|
89
|
+
canvas.width = sizeInPixels[0];
|
|
90
|
+
canvas.height = sizeInPixels[1];
|
|
91
|
+
container.appendChild(canvas);
|
|
92
|
+
return container;
|
|
93
|
+
}
|
|
50
94
|
export {
|
|
51
95
|
MapRenderer
|
|
52
96
|
};
|
|
@@ -1,21 +1,66 @@
|
|
|
1
1
|
import { FeatureRenderer } from './FeatureRenderer';
|
|
2
2
|
export class TextLayerRenderer {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @param {import("../layers/TextLayer").TextLayer} layer
|
|
5
|
+
*/
|
|
6
|
+
constructor(layer: import('../layers/TextLayer').TextLayer);
|
|
7
|
+
/**
|
|
8
|
+
* @type {import("../layers/TextLayer").TextLayer}
|
|
9
|
+
*/
|
|
10
|
+
layer: import('../layers/TextLayer').TextLayer;
|
|
5
11
|
featureRenderer: FeatureRenderer;
|
|
6
12
|
_element: HTMLDivElement;
|
|
7
|
-
|
|
13
|
+
/**
|
|
14
|
+
* @param {import("./MapRenderer").CanvasSingleton} canvasSingleton
|
|
15
|
+
*/
|
|
16
|
+
renderFrame(frameState: any, canvasSingleton: import('./MapRenderer').CanvasSingleton): HTMLDivElement;
|
|
8
17
|
getTextElementWithID(id: any): Element;
|
|
9
|
-
styleTextElement(element: any, textStyle: any, position: any):
|
|
18
|
+
styleTextElement(element: any, textStyle: any, position: any): {
|
|
19
|
+
width: any;
|
|
20
|
+
height: any;
|
|
21
|
+
};
|
|
10
22
|
getElementSize(element: any): {
|
|
11
23
|
width: any;
|
|
12
24
|
height: any;
|
|
13
25
|
};
|
|
14
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @param {{ height: number, width: number }} dimens
|
|
28
|
+
* @param {import("../styles/Text").Text} textStyle
|
|
29
|
+
* @param {{x: number, y: number}} position
|
|
30
|
+
*/
|
|
31
|
+
getElementBBox(dimens: {
|
|
32
|
+
height: number;
|
|
33
|
+
width: number;
|
|
34
|
+
}, textStyle: import('../styles/Text').Text, position: {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
}): {
|
|
15
38
|
minX: number;
|
|
16
39
|
minY: number;
|
|
17
40
|
maxX: number;
|
|
18
41
|
maxY: number;
|
|
19
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* @param {import("../styles/Text").Text} textStyle
|
|
45
|
+
* @param {{x: number, y: number}} position
|
|
46
|
+
*/
|
|
47
|
+
getElementPosition(textStyle: import('../styles/Text').Text, position: {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
}): {
|
|
51
|
+
left: string;
|
|
52
|
+
top: string;
|
|
53
|
+
};
|
|
20
54
|
getCollisionBoxElement(bbox: any): HTMLDivElement;
|
|
55
|
+
/**
|
|
56
|
+
* Draws a `Text` element's icon on the canvas.
|
|
57
|
+
*
|
|
58
|
+
* Expects the canvas context to be translated to the text element's position.
|
|
59
|
+
*
|
|
60
|
+
* TODO: support more shapes?
|
|
61
|
+
*
|
|
62
|
+
* @param {CanvasRenderingContext2D} context
|
|
63
|
+
* @param {import("../styles/Text").IconOptions} icon
|
|
64
|
+
*/
|
|
65
|
+
drawTextIcon(context: CanvasRenderingContext2D, icon: import('../styles/Text').IconOptions): void;
|
|
21
66
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { FeatureRenderer } from "./FeatureRenderer.js";
|
|
2
2
|
import { replaceChildren } from "../util/dom.js";
|
|
3
3
|
class TextLayerRenderer {
|
|
4
|
+
/**
|
|
5
|
+
* @param {import("../layers/TextLayer").TextLayer} layer
|
|
6
|
+
*/
|
|
4
7
|
constructor(layer) {
|
|
5
8
|
this.layer = layer;
|
|
6
9
|
this.featureRenderer = new FeatureRenderer();
|
|
@@ -13,13 +16,18 @@ class TextLayerRenderer {
|
|
|
13
16
|
style.pointerEvents = "none";
|
|
14
17
|
style.overflow = "hidden";
|
|
15
18
|
}
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @param {import("./MapRenderer").CanvasSingleton} canvasSingleton
|
|
21
|
+
*/
|
|
22
|
+
renderFrame(frameState, canvasSingleton) {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
if (this.layer.opacity === 0) return null;
|
|
18
25
|
const { declutterTree } = frameState;
|
|
19
26
|
const { projection, viewPortSize, sizeInPixels, visibleExtent, transform } = frameState.viewState;
|
|
20
|
-
this._element.style.opacity = this.layer.opacity
|
|
27
|
+
this._element.style.opacity = `${this.layer.opacity}`;
|
|
21
28
|
const source = this.layer.source;
|
|
22
29
|
const features = source.getFeaturesInExtent(visibleExtent);
|
|
30
|
+
let canvasCtx;
|
|
23
31
|
const textElements = [];
|
|
24
32
|
for (const feature of features) {
|
|
25
33
|
const geometries = feature.getProjectedGeometries(projection);
|
|
@@ -30,16 +38,24 @@ class TextLayerRenderer {
|
|
|
30
38
|
);
|
|
31
39
|
}
|
|
32
40
|
const styleFunction = feature.getStyleFunction() || this.layer.getStyleFunction();
|
|
33
|
-
const featureStyle = styleFunction(feature);
|
|
41
|
+
const featureStyle = styleFunction(feature, transform.k);
|
|
34
42
|
const textElement = this.getTextElementWithID(feature.uid);
|
|
35
43
|
textElement.innerText = featureStyle.text.content;
|
|
36
|
-
const [
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.
|
|
42
|
-
|
|
44
|
+
const [canvasX, canvasY] = transform.apply(point.coordinates);
|
|
45
|
+
const [relativeX, relativeY] = [
|
|
46
|
+
canvasX / sizeInPixels[0],
|
|
47
|
+
canvasY / sizeInPixels[1]
|
|
48
|
+
];
|
|
49
|
+
const position = this.getElementPosition(featureStyle.text, {
|
|
50
|
+
x: relativeX,
|
|
51
|
+
y: relativeY
|
|
52
|
+
});
|
|
53
|
+
const elementDimens = this.styleTextElement(
|
|
54
|
+
textElement,
|
|
55
|
+
featureStyle.text,
|
|
56
|
+
position
|
|
57
|
+
);
|
|
58
|
+
const bbox = this.getElementBBox(elementDimens, featureStyle.text, {
|
|
43
59
|
x: relativeX * viewPortSize[0],
|
|
44
60
|
y: relativeY * viewPortSize[1]
|
|
45
61
|
});
|
|
@@ -49,6 +65,46 @@ class TextLayerRenderer {
|
|
|
49
65
|
}
|
|
50
66
|
declutterTree.insert(bbox);
|
|
51
67
|
}
|
|
68
|
+
const callout = (_a = featureStyle == null ? void 0 : featureStyle.text) == null ? void 0 : _a.callout;
|
|
69
|
+
const icon = (_b = featureStyle == null ? void 0 : featureStyle.text) == null ? void 0 : _b.icon;
|
|
70
|
+
if (callout || icon) {
|
|
71
|
+
canvasCtx ?? (canvasCtx = canvasSingleton.getContext2d());
|
|
72
|
+
}
|
|
73
|
+
if (callout) {
|
|
74
|
+
const canvasOffsetX = (callout.offsetBy.x - callout.leaderGap) * window.devicePixelRatio;
|
|
75
|
+
const canvasOffsetY = callout.offsetBy.y * window.devicePixelRatio;
|
|
76
|
+
canvasCtx.beginPath();
|
|
77
|
+
canvasCtx.moveTo(canvasX, canvasY);
|
|
78
|
+
canvasCtx.lineTo(canvasX + canvasOffsetX / 2, canvasY + canvasOffsetY);
|
|
79
|
+
canvasCtx.moveTo(canvasX + canvasOffsetX / 2, canvasY + canvasOffsetY);
|
|
80
|
+
canvasCtx.lineTo(canvasX + canvasOffsetX, canvasY + canvasOffsetY);
|
|
81
|
+
canvasCtx.strokeStyle = callout.leaderColor;
|
|
82
|
+
canvasCtx.lineWidth = callout.leaderWidth;
|
|
83
|
+
canvasCtx.stroke();
|
|
84
|
+
canvasCtx.closePath();
|
|
85
|
+
}
|
|
86
|
+
if (icon) {
|
|
87
|
+
canvasCtx.beginPath();
|
|
88
|
+
canvasCtx.save();
|
|
89
|
+
let iconPosX = relativeX * viewPortSize[0];
|
|
90
|
+
let iconPosY = relativeY * viewPortSize[1];
|
|
91
|
+
if (callout) {
|
|
92
|
+
iconPosX += callout.offsetBy.x;
|
|
93
|
+
iconPosY += callout.offsetBy.y;
|
|
94
|
+
}
|
|
95
|
+
if (icon.position === "right") {
|
|
96
|
+
iconPosX += elementDimens.width;
|
|
97
|
+
} else if (icon.position === "left") {
|
|
98
|
+
iconPosX += icon.padding + icon.size / 2;
|
|
99
|
+
}
|
|
100
|
+
canvasCtx.translate(
|
|
101
|
+
iconPosX * window.devicePixelRatio,
|
|
102
|
+
iconPosY * window.devicePixelRatio
|
|
103
|
+
);
|
|
104
|
+
this.drawTextIcon(canvasCtx, icon);
|
|
105
|
+
canvasCtx.restore();
|
|
106
|
+
canvasCtx.closePath();
|
|
107
|
+
}
|
|
52
108
|
if (this.layer.drawCollisionBoxes) {
|
|
53
109
|
const collisionBoxDebugElement = this.getCollisionBoxElement(bbox);
|
|
54
110
|
textElements.push(collisionBoxDebugElement);
|
|
@@ -82,6 +138,7 @@ class TextLayerRenderer {
|
|
|
82
138
|
style.textShadow = textStyle.textShadow;
|
|
83
139
|
const { width, height } = this.getElementSize(element);
|
|
84
140
|
style.transform = textStyle.getTransform(width, height);
|
|
141
|
+
return { width, height };
|
|
85
142
|
}
|
|
86
143
|
getElementSize(element) {
|
|
87
144
|
if (!element.parentElement) {
|
|
@@ -93,29 +150,85 @@ class TextLayerRenderer {
|
|
|
93
150
|
}
|
|
94
151
|
return { width, height };
|
|
95
152
|
}
|
|
96
|
-
|
|
97
|
-
|
|
153
|
+
/**
|
|
154
|
+
* @param {{ height: number, width: number }} dimens
|
|
155
|
+
* @param {import("../styles/Text").Text} textStyle
|
|
156
|
+
* @param {{x: number, y: number}} position
|
|
157
|
+
*/
|
|
158
|
+
getElementBBox(dimens, textStyle, position) {
|
|
159
|
+
const collisionPadding = {
|
|
98
160
|
top: 2,
|
|
99
161
|
right: 2,
|
|
100
162
|
bottom: 2,
|
|
101
163
|
left: 2
|
|
102
164
|
};
|
|
103
|
-
const { width, height } = this.getElementSize(element);
|
|
104
165
|
const { x: translateX, y: translateY } = textStyle.getTranslation(
|
|
105
|
-
width,
|
|
106
|
-
height
|
|
166
|
+
dimens.width,
|
|
167
|
+
dimens.height
|
|
107
168
|
);
|
|
108
|
-
|
|
109
|
-
|
|
169
|
+
let minX = position.x + translateX - collisionPadding.left;
|
|
170
|
+
let minY = position.y + translateY - collisionPadding.top;
|
|
171
|
+
let maxX = minX + dimens.width + collisionPadding.left + collisionPadding.right;
|
|
172
|
+
let maxY = minY + dimens.height + collisionPadding.top + collisionPadding.bottom;
|
|
173
|
+
if (textStyle.callout) {
|
|
174
|
+
minX += textStyle.callout.offsetBy.x;
|
|
175
|
+
minY += textStyle.callout.offsetBy.y;
|
|
176
|
+
maxX += textStyle.callout.offsetBy.x;
|
|
177
|
+
maxY += textStyle.callout.offsetBy.y;
|
|
178
|
+
}
|
|
179
|
+
if (textStyle.icon) {
|
|
180
|
+
if (textStyle.icon.position === "left" || textStyle.icon.position === "right") {
|
|
181
|
+
maxX += textStyle.icon.size + textStyle.icon.padding;
|
|
182
|
+
}
|
|
183
|
+
const iconSizeHeightDiff = textStyle.icon.size - dimens.height;
|
|
184
|
+
if (iconSizeHeightDiff > 0) {
|
|
185
|
+
minY -= iconSizeHeightDiff / 2;
|
|
186
|
+
maxY += iconSizeHeightDiff / 2;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
minX = Math.floor(minX);
|
|
190
|
+
minY = Math.floor(minY);
|
|
191
|
+
maxX = Math.ceil(maxX);
|
|
192
|
+
maxY = Math.ceil(maxY);
|
|
110
193
|
return {
|
|
111
194
|
minX,
|
|
112
195
|
minY,
|
|
113
|
-
maxX
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
196
|
+
maxX,
|
|
197
|
+
maxY
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* @param {import("../styles/Text").Text} textStyle
|
|
202
|
+
* @param {{x: number, y: number}} position
|
|
203
|
+
*/
|
|
204
|
+
getElementPosition(textStyle, position) {
|
|
205
|
+
if (textStyle.callout) {
|
|
206
|
+
if (textStyle.icon.position === "left") {
|
|
207
|
+
return {
|
|
208
|
+
left: `calc(${position.x * 100}% + ${textStyle.callout.offsetBy.x + (textStyle.icon.size + textStyle.icon.padding * 2)}px)`,
|
|
209
|
+
top: `calc(${position.y * 100}% + ${textStyle.callout.offsetBy.y}px)`
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (textStyle.icon.position === "right") {
|
|
213
|
+
return {
|
|
214
|
+
left: `calc(${position.x * 100}% + ${textStyle.callout.offsetBy.x}px)`,
|
|
215
|
+
top: `calc(${position.y * 100}% + ${textStyle.callout.offsetBy.y}px)`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
left: `calc(${position.x * 100}% + ${textStyle.callout.offsetBy.x}px)`,
|
|
220
|
+
top: `calc(${position.y * 100}% + ${textStyle.callout.offsetBy.y}px)`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (textStyle.icon && (textStyle.icon.position === "left" || textStyle.icon.position === "right")) {
|
|
224
|
+
return {
|
|
225
|
+
left: `calc(${position.x * 100}% + ${textStyle.icon.size + textStyle.icon.padding * 2}px)`,
|
|
226
|
+
top: `${position.y * 100}%`
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
left: `${position.x * 100}%`,
|
|
231
|
+
top: `${position.y * 100}%`
|
|
119
232
|
};
|
|
120
233
|
}
|
|
121
234
|
getCollisionBoxElement(bbox) {
|
|
@@ -129,6 +242,40 @@ class TextLayerRenderer {
|
|
|
129
242
|
style.border = "1px solid red";
|
|
130
243
|
return element;
|
|
131
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Draws a `Text` element's icon on the canvas.
|
|
247
|
+
*
|
|
248
|
+
* Expects the canvas context to be translated to the text element's position.
|
|
249
|
+
*
|
|
250
|
+
* TODO: support more shapes?
|
|
251
|
+
*
|
|
252
|
+
* @param {CanvasRenderingContext2D} context
|
|
253
|
+
* @param {import("../styles/Text").IconOptions} icon
|
|
254
|
+
*/
|
|
255
|
+
drawTextIcon(context, icon) {
|
|
256
|
+
var _a;
|
|
257
|
+
if (icon.shape === "circle") {
|
|
258
|
+
context.arc(
|
|
259
|
+
0,
|
|
260
|
+
0,
|
|
261
|
+
icon.size * devicePixelRatio / 2,
|
|
262
|
+
0,
|
|
263
|
+
2 * Math.PI,
|
|
264
|
+
false
|
|
265
|
+
);
|
|
266
|
+
if (icon.style) {
|
|
267
|
+
(_a = icon.style.fill) == null ? void 0 : _a.drawInContext(context);
|
|
268
|
+
if (icon.style.stroke) {
|
|
269
|
+
context.lineWidth = icon.style.stroke.width;
|
|
270
|
+
context.strokeStyle = icon.style.stroke.getRgba();
|
|
271
|
+
context.stroke();
|
|
272
|
+
}
|
|
273
|
+
} else if (icon.color) {
|
|
274
|
+
context.fillStyle = icon.color;
|
|
275
|
+
context.fill();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
132
279
|
}
|
|
133
280
|
export {
|
|
134
281
|
TextLayerRenderer
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { FeatureRenderer } from './FeatureRenderer';
|
|
2
2
|
export class VectorLayerRenderer {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @constructor
|
|
5
|
+
* @param {import('../layers').VectorLayer} layer
|
|
6
|
+
*/
|
|
7
|
+
constructor(layer: import('../layers').VectorLayer);
|
|
8
|
+
/**
|
|
9
|
+
* @type {import('../layers').VectorLayer}
|
|
10
|
+
*/
|
|
11
|
+
layer: import('../layers').VectorLayer;
|
|
5
12
|
featureRenderer: FeatureRenderer;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
/**
|
|
14
|
+
* @param {import("./MapRenderer").CanvasSingleton} canvasSingleton
|
|
15
|
+
*/
|
|
16
|
+
renderFrame(frameState: any, canvasSingleton: import('./MapRenderer').CanvasSingleton): any;
|
|
10
17
|
}
|