in src/lib/components/molecules/canvas-map/lib/renderers/TextLayerRenderer.js [38:189]
renderFrame(frameState, canvas) {
if (this.layer.opacity === 0) return null
const { declutterTree } = frameState
const { projection, viewPortSize, sizeInPixels, visibleExtent, transform } =
frameState.viewState
// set opacity
this._element.style.opacity = `${this.layer.opacity}`
const source = this.layer.source
const features = source.getFeaturesInExtent(visibleExtent)
/** @type {CanvasRenderingContext2D} */
let canvasCtx
const textElements = []
for (const feature of features) {
// get point geometry
const geometries = feature.getProjectedGeometries(projection)
const point = geometries.find((d) => d.type === "Point")
if (!point) {
throw new Error(
`Expected Point geometry for feature in TextLayer: ${feature}`,
)
}
const styleFunction =
feature.getStyleFunction() || this.layer.getStyleFunction()
const featureStyle = styleFunction(
feature,
transform.k,
this._hoveredFeature === feature,
)
// get text element
const textElement = this.getTextElementWithID(feature.uid)
textElement.innerText = featureStyle.text.content
const [canvasX, canvasY] = featureStyle.text.callout
? transform.apply(
projection([
featureStyle.text.callout.offsetTo.x,
featureStyle.text.callout.offsetTo.y,
]),
)
: transform.apply(point.coordinates)
const [relativeX, relativeY] = [
canvasX / sizeInPixels[0],
canvasY / sizeInPixels[1],
]
const position = {
left: `${relativeX * 100}%`,
top: `${relativeY * 100}%`,
}
// Apply style to text element, and receive measured size from DOM
const elementDimens = this.styleTextElement(
textElement,
featureStyle.text,
position,
)
const bbox = this.getElementBBox(
elementDimens,
featureStyle.text,
{
x: relativeX * viewPortSize[0],
y: relativeY * viewPortSize[1],
},
this.layer.declutterBoundingBoxPadding,
)
// skip item if it collides with existing elements
if (declutterTree) {
if (declutterTree.collides(bbox)) {
continue
}
// add element to declutter tree to prevent collisions
declutterTree.insert(bbox)
}
const callout = featureStyle?.text?.callout
const icon = featureStyle?.text?.icon
if (callout || icon) {
canvasCtx ??= canvas.getContext("2d")
}
if (callout) {
const [originalX, originalY] = transform.apply(point.coordinates)
const offsetDiffX = canvasX - originalX
const offsetDiffY = canvasY - originalY
canvasCtx.beginPath()
canvasCtx.moveTo(originalX, originalY)
canvasCtx.lineTo(originalX + offsetDiffX / 2, originalY + offsetDiffY)
canvasCtx.moveTo(originalX + offsetDiffX / 2, canvasY)
canvasCtx.lineTo(canvasX, canvasY)
canvasCtx.strokeStyle = callout.leaderColor
canvasCtx.lineWidth = callout.leaderWidth
canvasCtx.stroke()
canvasCtx.closePath()
}
// TODO: should we draw icons with SVG? add them into the textlayer? it'd make a lot of the
// maths easier!
if (icon) {
canvasCtx.beginPath()
canvasCtx.save()
let iconPosX = relativeX * viewPortSize[0]
let iconPosY = relativeY * viewPortSize[1]
if (icon.position === "right") {
iconPosX += elementDimens.width
} else if (icon.position === "left") {
iconPosX += icon.padding + icon.size / 2
}
canvasCtx.translate(
iconPosX * window.devicePixelRatio,
iconPosY * window.devicePixelRatio,
)
this.drawTextIcon(canvasCtx, icon)
canvasCtx.restore()
canvasCtx.closePath()
}
if (this.layer.drawCollisionBoxes) {
const collisionBoxDebugElement = this.getCollisionBoxElement(bbox)
textElements.push(collisionBoxDebugElement)
}
textElements.push(textElement)
}
replaceChildren(this._element, textElements)
return this._element
}