UNPKG

@deck.gl/widgets

Version:

UI widgets for deck.gl

175 lines 8.17 kB
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime"; // deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { Widget } from '@deck.gl/core'; import { render } from 'preact'; export class InfoWidget extends Widget { constructor(props) { super(props); this.className = 'deck-widget-info'; this.placement = 'fill'; this.setProps(this.props); } setProps(props) { this.viewId = props.viewId ?? this.viewId; super.setProps(props); } onCreateRootElement() { const element = super.onCreateRootElement(); const style = { margin: '0px', top: '0px', left: '0px', position: 'absolute' }; Object.entries(style).forEach(([key, value]) => element.style.setProperty(key, value)); return element; } onViewportChange(viewport) { this.viewport = viewport; this.updateHTML(); } onHover(info) { if (this.props.mode === 'hover' && this.props.getTooltip) { const tooltip = this.props.getTooltip(info, this); // hover tooltips should show over static and click infos this.setProps({ visible: tooltip !== null, ...tooltip, style: { zIndex: '1', ...tooltip?.style } }); } } onClick(info) { if (this.props.mode === 'click' && this.props.getTooltip) { const tooltip = this.props.getTooltip(info, this); this.setProps({ visible: tooltip !== null, ...tooltip }); return tooltip !== null; } // Original behavior return this.props.onClick?.(this, info) || false; } onAdd({ deck, viewId }) { this.deck = deck; if (!viewId) { this.viewport = deck.getViewports()[0]; } else { this.viewport = deck.getViewports().find(viewport => viewport.id === viewId); } } onRenderHTML(rootElement) { if (!this.viewport) { return; } const [longitude, latitude] = this.props.position; // Project the clicked geographic coordinate to canvas (x, y) const [x, y] = this.viewport.project([longitude, latitude]); const minOffset = this.props.minOffset || 0; const gap = 10; // gap between clicked point and popup box const arrowHeight = 8; // height of the triangle arrow const arrowWidth = 16; // full width of the arrow // Decide vertical orientation. // If the clicked point is in the bottom half, place the popup above the point. const isAbove = y > this.viewport.height / 2; const background = (this.props.style && this.props.style.background) || 'rgba(255,255,255,0.9)'; // Render the popup container with a content box and a placeholder for the arrow. // The container is positioned absolutely (initially at 0,0) and will be repositioned after measuring. const ui = this.props.visible ? (_jsxs("div", { className: "popup-container", style: { position: 'absolute', left: 0, top: 0 }, children: [_jsx("div", { className: "popup-content", style: { background, padding: '10px', position: 'relative', // Include any additional styles ...this.props.style }, children: this.props.text }), _jsx("div", { className: "popup-arrow", style: { position: 'absolute', width: '0px', height: '0px' } })] })) : null; render(ui, rootElement); // After rendering, measure the content and adjust positioning so that: // - The popup (with its arrow) does not cover the clicked point. // - The arrow's tip points to the clicked point. // - The popup remains within the canvas bounds, with minOffset. // eslint-disable-next-line max-statements, complexity requestAnimationFrame(() => { if (!this.props.visible || !rootElement.firstChild || !this.viewport) return; const container = rootElement.firstChild; const contentEl = container.querySelector('.popup-content'); const arrowEl = container.querySelector('.popup-arrow'); if (!contentEl || !arrowEl) return; const contentRect = contentEl.getBoundingClientRect(); const popupWidth = contentRect.width; const popupHeight = contentRect.height; // Compute ideal horizontal position: center the popup on the clicked x let computedLeft = x - popupWidth / 2; // Compute vertical position based on orientation: // If the popup is above, position its bottom edge (minus arrow & gap) at the clicked y. // Otherwise, position its top edge (plus arrow & gap) at the clicked y. let computedTop; if (isAbove) { computedTop = y - gap - arrowHeight - popupHeight; } else { computedTop = y + gap + arrowHeight; } // Ensure the popup (content) stays within horizontal bounds of the canvas if (computedLeft < minOffset) { computedLeft = minOffset; } if (computedLeft + popupWidth > this.viewport.width - minOffset) { computedLeft = this.viewport.width - minOffset - popupWidth; } // Ensure the vertical position (including the arrow) stays within canvas bounds. if (isAbove) { if (computedTop < minOffset) { computedTop = minOffset; } } else if (computedTop + popupHeight + arrowHeight > this.viewport.height - minOffset) { computedTop = this.viewport.height - minOffset - popupHeight - arrowHeight; } // Update container position (remove any transform and set left/top explicitly) container.style.left = `${computedLeft}px`; container.style.top = `${computedTop}px`; container.style.transform = ''; // Compute arrow's horizontal offset relative to the container. // We want the arrow's tip to align with the clicked point. let arrowLeft = x - computedLeft - arrowWidth / 2; // Optionally constrain arrowLeft so it stays within the popup's width. arrowLeft = Math.max(arrowLeft, 0); arrowLeft = Math.min(arrowLeft, popupWidth - arrowWidth); // Update arrow element's style based on orientation. if (isAbove) { // Popup is above the clicked point so arrow appears at the bottom of the popup. arrowEl.style.left = `${arrowLeft}px`; arrowEl.style.bottom = `-${arrowHeight}px`; arrowEl.style.top = ''; arrowEl.style.borderLeft = `${arrowWidth / 2}px solid transparent`; arrowEl.style.borderRight = `${arrowWidth / 2}px solid transparent`; arrowEl.style.borderTop = `${arrowHeight}px solid ${background}`; arrowEl.style.borderBottom = ''; } else { // Popup is below the clicked point so arrow appears at the top. arrowEl.style.left = `${arrowLeft}px`; arrowEl.style.top = `-${arrowHeight}px`; arrowEl.style.bottom = ''; arrowEl.style.borderLeft = `${arrowWidth / 2}px solid transparent`; arrowEl.style.borderRight = `${arrowWidth / 2}px solid transparent`; arrowEl.style.borderBottom = `${arrowHeight}px solid ${background}`; arrowEl.style.borderTop = ''; } }); } } InfoWidget.defaultProps = { ...Widget.defaultProps, id: 'info', position: [0, 0], text: '', visible: false, minOffset: 0, viewId: null, mode: 'hover', getTooltip: undefined, onClick: undefined }; //# sourceMappingURL=info-widget.js.map