@deck.gl-community/react 9.0.1

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 (34) hide show
  1. package/README.md +3 -0
  2. package/dist/components/icon.d.ts +3 -0
  3. package/dist/components/icon.js +6 -0
  4. package/dist/components/long-press-button.d.ts +13 -0
  5. package/dist/components/long-press-button.js +31 -0
  6. package/dist/components/modal.d.ts +8 -0
  7. package/dist/components/modal.js +70 -0
  8. package/dist/components/positioned-view-control.d.ts +9 -0
  9. package/dist/components/positioned-view-control.js +5 -0
  10. package/dist/components/view-control.d.ts +38 -0
  11. package/dist/components/view-control.js +128 -0
  12. package/dist/index.cjs +521 -0
  13. package/dist/index.cjs.map +7 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.js +13 -0
  16. package/dist/overlays/html-cluster-overlay.d.ts +13 -0
  17. package/dist/overlays/html-cluster-overlay.js +53 -0
  18. package/dist/overlays/html-overlay-item.d.ts +12 -0
  19. package/dist/overlays/html-overlay-item.js +13 -0
  20. package/dist/overlays/html-overlay.d.ts +19 -0
  21. package/dist/overlays/html-overlay.js +65 -0
  22. package/dist/overlays/html-tooltip-overlay.d.ts +16 -0
  23. package/dist/overlays/html-tooltip-overlay.js +53 -0
  24. package/package.json +50 -0
  25. package/src/components/icon.tsx +7 -0
  26. package/src/components/long-press-button.tsx +43 -0
  27. package/src/components/modal.tsx +92 -0
  28. package/src/components/positioned-view-control.tsx +16 -0
  29. package/src/components/view-control.tsx +163 -0
  30. package/src/index.ts +19 -0
  31. package/src/overlays/html-cluster-overlay.ts +80 -0
  32. package/src/overlays/html-overlay-item.tsx +30 -0
  33. package/src/overlays/html-overlay.tsx +91 -0
  34. package/src/overlays/html-tooltip-overlay.tsx +74 -0
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ type Props = {
3
+ x?: number;
4
+ y?: number;
5
+ coordinates: number[];
6
+ children: any;
7
+ style?: Record<string, any>;
8
+ };
9
+ export declare class HtmlOverlayItem extends React.Component<Props> {
10
+ render(): React.JSX.Element;
11
+ }
12
+ export {};
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ export class HtmlOverlayItem extends React.Component {
3
+ render() {
4
+ const { x, y, children, style, coordinates, ...props } = this.props;
5
+ const { zIndex = 'auto', ...remainingStyle } = style || {};
6
+ return (
7
+ // Using transform translate to position overlay items will result in a smooth zooming
8
+ // effect, whereas using the top/left css properties will cause overlay items to
9
+ // jiggle when zooming
10
+ React.createElement("div", { style: { transform: `translate(${x}px, ${y}px)`, position: 'absolute', zIndex } },
11
+ React.createElement("div", { style: { userSelect: 'none', ...remainingStyle }, ...props }, children)));
12
+ }
13
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ interface Props {
3
+ viewport?: Record<string, any>;
4
+ zIndex?: number;
5
+ children?: React.ReactNode;
6
+ overflowMargin?: number;
7
+ }
8
+ export declare class HtmlOverlay extends React.Component<Props> {
9
+ static deckGLViewProps: boolean;
10
+ getItems(): Array<any>;
11
+ getCoords(coordinates: number[]): [number, number];
12
+ inView([x, y]: number[]): boolean;
13
+ scaleWithZoom(n: number): number;
14
+ breakpointWithZoom(threshold: number, a: any, b: any): any;
15
+ getViewport(): Record<string, any>;
16
+ getZoom(): any;
17
+ render(): React.JSX.Element;
18
+ }
19
+ export {};
@@ -0,0 +1,65 @@
1
+ import * as React from 'react';
2
+ const styles = {
3
+ mainContainer: {
4
+ width: '100%',
5
+ height: '100%',
6
+ position: 'absolute',
7
+ pointerEvents: 'none',
8
+ overflow: 'hidden'
9
+ }
10
+ };
11
+ export class HtmlOverlay extends React.Component {
12
+ // This is needed for Deck.gl 8.0+
13
+ static deckGLViewProps = true;
14
+ // Override this to provide your items
15
+ getItems() {
16
+ const { children } = this.props;
17
+ if (children) {
18
+ return Array.isArray(children) ? children : [children];
19
+ }
20
+ return [];
21
+ }
22
+ getCoords(coordinates) {
23
+ const pos = this.props.viewport.project(coordinates);
24
+ if (!pos)
25
+ return [-1, -1];
26
+ return pos;
27
+ }
28
+ inView([x, y]) {
29
+ const { viewport, overflowMargin = 0 } = this.props;
30
+ const { width, height } = viewport;
31
+ return !(x < -overflowMargin ||
32
+ y < -overflowMargin ||
33
+ x > width + overflowMargin ||
34
+ y > height + overflowMargin);
35
+ }
36
+ scaleWithZoom(n) {
37
+ const { zoom } = this.props.viewport;
38
+ return n / Math.pow(2, 20 - zoom);
39
+ }
40
+ breakpointWithZoom(threshold, a, b) {
41
+ const { zoom } = this.props.viewport;
42
+ return zoom > threshold ? a : b;
43
+ }
44
+ getViewport() {
45
+ return this.props.viewport;
46
+ }
47
+ getZoom() {
48
+ return this.props.viewport.zoom;
49
+ }
50
+ render() {
51
+ const { zIndex = 1 } = this.props;
52
+ const style = Object.assign({ zIndex }, styles.mainContainer);
53
+ const renderItems = [];
54
+ this.getItems()
55
+ .filter(Boolean)
56
+ .forEach((item, index) => {
57
+ const [x, y] = this.getCoords(item.props.coordinates);
58
+ if (this.inView([x, y])) {
59
+ const key = item.key === null || item.key === undefined ? index : item.key;
60
+ renderItems.push(React.cloneElement(item, { x, y, key }));
61
+ }
62
+ });
63
+ return React.createElement("div", { style: style }, renderItems);
64
+ }
65
+ }
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { HtmlOverlay } from './html-overlay';
3
+ type State = {
4
+ visible: boolean;
5
+ pickingInfo: Record<string, any> | null | undefined;
6
+ };
7
+ export declare class HtmlTooltipOverlay extends HtmlOverlay {
8
+ constructor(props: any);
9
+ componentWillMount(): void;
10
+ timeoutID: number | null | undefined;
11
+ state: State;
12
+ _getTooltip(pickingInfo: Record<string, any>): string;
13
+ _makeOverlay(): React.JSX.Element;
14
+ getItems(): Array<Record<string, any> | null | undefined>;
15
+ }
16
+ export {};
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import { HtmlOverlay } from './html-overlay';
3
+ import { HtmlOverlayItem } from './html-overlay-item';
4
+ const styles = {
5
+ tooltip: {
6
+ transform: 'translate(-50%,-100%)',
7
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
8
+ padding: '4px 8px',
9
+ borderRadius: 8,
10
+ color: 'white'
11
+ }
12
+ };
13
+ const SHOW_TOOLTIP_TIMEOUT = 250;
14
+ export class HtmlTooltipOverlay extends HtmlOverlay {
15
+ constructor(props) {
16
+ super(props);
17
+ this.state = { visible: false, pickingInfo: null };
18
+ }
19
+ componentWillMount() {
20
+ this.context.nebula.queryObjectEvents.on('pick', ({ event, pickingInfo }) => {
21
+ if (this.timeoutID !== null) {
22
+ window.clearTimeout(this.timeoutID);
23
+ }
24
+ this.timeoutID = null;
25
+ if (pickingInfo && this._getTooltip(pickingInfo)) {
26
+ this.timeoutID = window.setTimeout(() => {
27
+ this.setState({ visible: true, pickingInfo });
28
+ }, SHOW_TOOLTIP_TIMEOUT);
29
+ }
30
+ else {
31
+ this.setState({ visible: false });
32
+ }
33
+ });
34
+ }
35
+ timeoutID = null;
36
+ state = undefined;
37
+ _getTooltip(pickingInfo) {
38
+ return pickingInfo.object.style.tooltip;
39
+ }
40
+ _makeOverlay() {
41
+ const { pickingInfo } = this.state;
42
+ if (pickingInfo) {
43
+ return (React.createElement(HtmlOverlayItem, { key: 0, coordinates: pickingInfo.lngLat, style: styles.tooltip }, this._getTooltip(pickingInfo)));
44
+ }
45
+ return null;
46
+ }
47
+ getItems() {
48
+ if (this.state.visible) {
49
+ return [this._makeOverlay()];
50
+ }
51
+ return [];
52
+ }
53
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@deck.gl-community/react",
3
+ "description": "React components for deck.gl",
4
+ "license": "MIT",
5
+ "version": "9.0.1",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/uber/@deck.gl-community"
9
+ },
10
+ "scripts": {
11
+ "test": "vitest run",
12
+ "test-watch": "vitest"
13
+ },
14
+ "keywords": [
15
+ "webgl",
16
+ "visualization",
17
+ "overlay",
18
+ "layer"
19
+ ],
20
+ "type": "module",
21
+ "sideEffects": false,
22
+ "types": "./dist/index.d.ts",
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "require": "./dist/index.cjs",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "src"
35
+ ],
36
+ "dependencies": {
37
+ "@turf/helpers": "^6.5.0",
38
+ "@types/styled-react-modal": "^1.2.5",
39
+ "boxicons": "^2.1.4",
40
+ "prop-types": "^15.8.1",
41
+ "styled-components": "^4.4.1",
42
+ "styled-react-modal": "^3.1.1",
43
+ "supercluster": "^8.0.1"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^16.14 || ^17",
47
+ "react-dom": "^16.14 || ^17"
48
+ },
49
+ "gitHead": "646f8328d27d67d596f05c9154072051ec5cf4f0"
50
+ }
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ import 'boxicons';
3
+
4
+ export function Icon(props) {
5
+ // @ts-expect-error TODO
6
+ return <box-icon color="currentColor" {...props} />;
7
+ }
@@ -0,0 +1,43 @@
1
+ import React, {PureComponent} from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ export class LongPressButton extends PureComponent {
5
+ static propTypes = {
6
+ onClick: PropTypes.func.isRequired,
7
+ // eslint-disable-next-line react/forbid-prop-types
8
+ children: PropTypes.any.isRequired
9
+ };
10
+
11
+ buttonPressTimer: ReturnType<typeof setTimeout> | null = null;
12
+
13
+ _repeat = () => {
14
+ if (this.buttonPressTimer) {
15
+ (this.props as any).onClick();
16
+ this.buttonPressTimer = setTimeout(this._repeat, 100);
17
+ }
18
+ };
19
+
20
+ _handleButtonPress = () => {
21
+ this.buttonPressTimer = setTimeout(this._repeat, 100);
22
+ };
23
+
24
+ _handleButtonRelease = () => {
25
+ if (this.buttonPressTimer) {
26
+ clearTimeout(this.buttonPressTimer);
27
+ }
28
+ this.buttonPressTimer = null;
29
+ };
30
+
31
+ render() {
32
+ return (
33
+ <div
34
+ onMouseDown={(event) => {
35
+ this._handleButtonPress();
36
+ document.addEventListener('mouseup', this._handleButtonRelease, {once: true});
37
+ }}
38
+ >
39
+ {(this.props as any).children}
40
+ </div>
41
+ );
42
+ }
43
+ }
@@ -0,0 +1,92 @@
1
+ /* eslint-env browser */
2
+
3
+ import * as React from 'react';
4
+ import Modal, {ModalProvider} from 'styled-react-modal';
5
+ import styled from 'styled-components';
6
+
7
+ export const Button = styled.button`
8
+ display: inline-block;
9
+ color: #fff;
10
+ background-color: rgb(90, 98, 94);
11
+ font-size: 1em;
12
+ margin: 0.25em;
13
+ padding: 0.375em 0.75em;
14
+ border: 1px solid transparent;
15
+ border-radius: 0.25em;
16
+ display: block;
17
+ `;
18
+
19
+ const StyledModal = Modal.styled`
20
+ position: relative;
21
+ display: block;
22
+ width: 50rem;
23
+ height: auto;
24
+ max-width: 500px;
25
+ margin: 1.75rem auto;
26
+ box-sizing: border-box;
27
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
28
+ font-size: 1rem;
29
+ font-weight: 400;
30
+ color: rgb(21, 25, 29);
31
+ line-height: 1.5;
32
+ text-align: left;
33
+ `;
34
+
35
+ const Content = styled.div`
36
+ position: relative;
37
+ display: flex;
38
+ flex-direction: column;
39
+ width: 100%;
40
+ pointer-events: auto;
41
+ background-color: #fff;
42
+ background-clip: padding-box;
43
+ border: 1px solid rgba(0, 0, 0, 0.2);
44
+ border-radius: 0.3rem;
45
+ outline: 0;
46
+ `;
47
+
48
+ const HeaderRow = styled.div`
49
+ display: flex;
50
+ align-items: flex-start;
51
+ justify-content: space-between;
52
+ padding: 0.75rem 0.75rem;
53
+ border-bottom: 1px solid rgb(222, 226, 230);
54
+ `;
55
+
56
+ const Header = styled.h5`
57
+ font-size: 1.25rem;
58
+ font-weight: 500;
59
+ margin: 0;
60
+ `;
61
+
62
+ export type ModalProps = {
63
+ title: any;
64
+ content: any;
65
+ onClose: () => unknown;
66
+ };
67
+
68
+ export function EditorModal(props: ModalProps) {
69
+ const [isOpen, setIsOpen] = React.useState(true);
70
+
71
+ function toggleModal() {
72
+ if (isOpen) {
73
+ props.onClose();
74
+ }
75
+ setIsOpen(!isOpen);
76
+ }
77
+
78
+ return (
79
+ <>
80
+ <ModalProvider>
81
+ <StyledModal isOpen={isOpen} onBackgroundClick={toggleModal} onEscapeKeydown={toggleModal}>
82
+ <Content>
83
+ <HeaderRow>
84
+ <Header>{props.title}</Header>
85
+ </HeaderRow>
86
+ {props.content}
87
+ </Content>
88
+ </StyledModal>
89
+ </ModalProvider>
90
+ </>
91
+ );
92
+ }
@@ -0,0 +1,16 @@
1
+ import {ViewControl} from './view-control';
2
+ import React from 'react';
3
+
4
+ // A wrapper for positioning the ViewControl component
5
+ export const PositionedViewControl = ({fitBounds, panBy, zoomBy, zoomLevel, maxZoom, minZoom}) => (
6
+ <div style={{position: 'relative', top: '20px', left: '20px'}}>
7
+ <ViewControl
8
+ fitBounds={fitBounds}
9
+ panBy={panBy}
10
+ zoomBy={zoomBy}
11
+ zoomLevel={zoomLevel}
12
+ maxZoom={maxZoom}
13
+ minZoom={minZoom}
14
+ />
15
+ </div>
16
+ );
@@ -0,0 +1,163 @@
1
+ // @ts-nocheck TODO
2
+
3
+ import React, {PureComponent} from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import styled from 'styled-components';
6
+ import {LongPressButton} from './long-press-button';
7
+
8
+ export const ViewControlWrapper = styled.div`
9
+ align-items: center;
10
+ display: flex;
11
+ flex-direction: column;
12
+ position: absolute;
13
+ z-index: 99;
14
+ user-select: none;
15
+ `;
16
+
17
+ export const NavigationButtonContainer = styled.div`
18
+ background: #f7f7f7;
19
+ border-radius: 23px;
20
+ border: 0.5px solid #eaeaea;
21
+ box-shadow: inset 11px 11px 5px -7px rgba(230, 230, 230, 0.49);
22
+ height: 46px;
23
+ width: 46px;
24
+ `;
25
+
26
+ export const NavigationButton = styled.div`
27
+ color: #848484;
28
+ cursor: pointer;
29
+ left: ${(props: any) => props.left};
30
+ position: absolute;
31
+ top: ${(props: any) => props.top};
32
+ transform: rotate(${(props: any) => props.rotate || 0}deg);
33
+
34
+ &:hover,
35
+ &:active {
36
+ color: #00ade6;
37
+ }
38
+ `;
39
+
40
+ export const ZoomControlWrapper = styled.div`
41
+ align-items: center;
42
+ background: #f7f7f7;
43
+ border: 0.5px solid #eaeaea;
44
+ display: flex;
45
+ flex-direction: column;
46
+ margin-top: 6px;
47
+ padding: 2px 0;
48
+ width: 18px;
49
+ `;
50
+
51
+ export const VerticalSlider = styled.div`
52
+ display: inline-block;
53
+ height: 100px;
54
+ padding: 0;
55
+ width: 10px;
56
+
57
+ > input[type='range'][orient='vertical'] {
58
+ -webkit-appearance: slider-vertical;
59
+ height: 100px;
60
+ padding: 0;
61
+ margin: 0;
62
+ width: 10px;
63
+ }
64
+ `;
65
+
66
+ export const ZoomControlButton = styled.div`
67
+ cursor: pointer;
68
+ font-size: 14px;
69
+ font-weight: 500;
70
+ margin: -4px;
71
+
72
+ &:hover,
73
+ &:active {
74
+ color: #00ade6;
75
+ }
76
+ `;
77
+
78
+ export class ViewControl extends PureComponent {
79
+ static displayName = 'ViewControl';
80
+
81
+ static propTypes = {
82
+ // functions
83
+ fitBounds: PropTypes.func,
84
+ panBy: PropTypes.func,
85
+ zoomBy: PropTypes.func,
86
+ // current zoom level
87
+ zoomLevel: PropTypes.number,
88
+ // configuration
89
+ minZoom: PropTypes.number,
90
+ maxZoom: PropTypes.number,
91
+ deltaPan: PropTypes.number,
92
+ deltaZoom: PropTypes.number
93
+ };
94
+
95
+ static defaultProps = {
96
+ fitBounds: () => {},
97
+ panBy: () => {},
98
+ zoomBy: () => {},
99
+ deltaPan: 10,
100
+ deltaZoom: 0.1,
101
+ minZoom: 0.1,
102
+ maxZoom: 1
103
+ };
104
+
105
+ // pan actions
106
+ panUp = () => (this.props as any).panBy(0, (this.props as any).deltaPan);
107
+ panDown = () => (this.props as any).panBy(0, -1 * (this.props as any).deltaPan);
108
+ panLeft = () => (this.props as any).panBy((this.props as any).deltaPan, 0);
109
+ panRight = () => (this.props as any).panBy(-1 * (this.props as any).deltaPan, 0);
110
+
111
+ // zoom actions
112
+ zoomIn = () => (this.props as any).zoomBy((this.props as any).deltaZoom);
113
+ zoomOut = () => (this.props as any).zoomBy(-1 * (this.props as any).deltaZoom);
114
+ onChangeZoomLevel = (evt) => {
115
+ const delta = evt.target.value - (this.props as any).zoomLevel;
116
+ (this.props as any).zoomBy(delta);
117
+ };
118
+
119
+ render() {
120
+ const buttons = [
121
+ {top: -2, left: 14, rotate: 0, onClick: this.panUp, content: '▲', key: 'up'},
122
+ {top: 12, left: 0, rotate: -90, onClick: this.panLeft, content: '◀', key: 'left'},
123
+ {top: 12, left: 28, rotate: 90, onClick: this.panRight, content: '▶', key: 'right'},
124
+ {top: 25, left: 14, rotate: 180, onClick: this.panDown, content: '▼', key: 'down'}
125
+ ];
126
+
127
+ return (
128
+ <ViewControlWrapper>
129
+ <NavigationButtonContainer>
130
+ {buttons.map((b: any) => (
131
+ <NavigationButton key={b.key} top={`${b.top}px`} left={`${b.left}px`} rotate={b.rotate}>
132
+ <LongPressButton onClick={b.onClick}>{b.content}</LongPressButton>
133
+ </NavigationButton>
134
+ ))}
135
+ {/* @ts-expect-error TODO */}
136
+ <NavigationButton top={'12px'} left={'16px'} onClick={this.props.fitBounds}>
137
+ {'¤'}
138
+ </NavigationButton>
139
+ </NavigationButtonContainer>
140
+ <ZoomControlWrapper>
141
+ <ZoomControlButton>
142
+ <LongPressButton onClick={this.zoomIn}>{'+'}</LongPressButton>
143
+ </ZoomControlButton>
144
+ <VerticalSlider>
145
+ <input
146
+ type="range"
147
+ value={(this.props as any).zoomLevel}
148
+ min={(this.props as any).minZoom}
149
+ max={(this.props as any).maxZoom}
150
+ step={(this.props as any).deltaZoom}
151
+ onChange={this.onChangeZoomLevel}
152
+ /* @ts-expect-error */
153
+ orient="vertical"
154
+ />
155
+ </VerticalSlider>
156
+ <ZoomControlButton>
157
+ <LongPressButton onClick={this.zoomOut}>{'-'}</LongPressButton>
158
+ </ZoomControlButton>
159
+ </ZoomControlWrapper>
160
+ </ViewControlWrapper>
161
+ );
162
+ }
163
+ }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ // Components (originally from @deck.gl-community/react-graph-layers)
2
+
3
+ export {LongPressButton} from './components/long-press-button';
4
+ export {ViewControl} from './components/view-control';
5
+ export {PositionedViewControl} from './components/positioned-view-control';
6
+
7
+ // Components (originally from @nebula.gl/editor)
8
+
9
+ export {EditorModal as Modal} from './components/modal';
10
+ export {Button} from './components/modal';
11
+ export {Icon} from './components/icon';
12
+
13
+ // Overlays (originally from @nebula.gl/overlays)
14
+
15
+ export {HtmlOverlay} from './overlays/html-overlay';
16
+ export {HtmlOverlayItem} from './overlays/html-overlay-item';
17
+ export {HtmlClusterOverlay} from './overlays/html-cluster-overlay';
18
+
19
+ export {HtmlTooltipOverlay} from './overlays/html-tooltip-overlay';
@@ -0,0 +1,80 @@
1
+ import {point} from '@turf/helpers';
2
+ import Supercluster from 'supercluster';
3
+ import {HtmlOverlay} from './html-overlay';
4
+
5
+ export class HtmlClusterOverlay<ObjType> extends HtmlOverlay {
6
+ _superCluster: Supercluster;
7
+ _lastObjects: ObjType[] | null | undefined = null;
8
+
9
+ getItems(): Record<string, any>[] {
10
+ // supercluster().load() is expensive and we want to run it only
11
+ // when necessary and not for every frame.
12
+
13
+ // TODO: Warn if this is running many times / sec
14
+
15
+ const newObjects = this.getAllObjects();
16
+ if (newObjects !== this._lastObjects) {
17
+ this._superCluster = new Supercluster(this.getClusterOptions());
18
+ this._superCluster.load(
19
+ newObjects.map((object) => point(this.getObjectCoordinates(object), {object}))
20
+ );
21
+ this._lastObjects = newObjects;
22
+ // console.log('new Supercluster() run');
23
+ }
24
+
25
+ const clusters = this._superCluster.getClusters(
26
+ [-180, -90, 180, 90],
27
+ Math.round(this.getZoom())
28
+ );
29
+
30
+ return clusters.map(
31
+ ({
32
+ geometry: {coordinates},
33
+ properties: {cluster, point_count: pointCount, cluster_id: clusterId, object}
34
+ }) =>
35
+ cluster
36
+ ? this.renderCluster(coordinates, clusterId, pointCount)
37
+ : this.renderObject(coordinates, object)
38
+ );
39
+ }
40
+
41
+ getClusterObjects(clusterId: number): ObjType[] {
42
+ return this._superCluster
43
+ .getLeaves(clusterId, Infinity)
44
+ .map((object) => object.properties.object);
45
+ }
46
+
47
+ // Override to provide items that need clustering.
48
+ // If the items have not changed please provide the same array to avoid
49
+ // regeneration of the cluster which causes performance issues.
50
+ getAllObjects(): ObjType[] {
51
+ return [];
52
+ }
53
+
54
+ // override to provide coordinates for each object of getAllObjects()
55
+ getObjectCoordinates(obj: ObjType): [number, number] {
56
+ return [0, 0];
57
+ }
58
+
59
+ // Get options object used when instantiating supercluster
60
+ getClusterOptions(): Record<string, any> | null | undefined {
61
+ return {
62
+ maxZoom: 20
63
+ };
64
+ }
65
+
66
+ // override to return an HtmlOverlayItem
67
+ renderObject(coordinates: number[], obj: ObjType): Record<string, any> | null | undefined {
68
+ return null;
69
+ }
70
+
71
+ // override to return an HtmlOverlayItem
72
+ // use getClusterObjects() to get cluster contents
73
+ renderCluster(
74
+ coordinates: number[],
75
+ clusterId: number,
76
+ pointCount: number
77
+ ): Record<string, any> | null | undefined {
78
+ return null;
79
+ }
80
+ }
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+
3
+ type Props = {
4
+ // Injected by HtmlOverlay
5
+ x?: number;
6
+ y?: number;
7
+
8
+ // User provided
9
+ coordinates: number[];
10
+ children: any;
11
+ style?: Record<string, any>;
12
+ };
13
+
14
+ export class HtmlOverlayItem extends React.Component<Props> {
15
+ render() {
16
+ const {x, y, children, style, coordinates, ...props} = this.props;
17
+ const {zIndex = 'auto', ...remainingStyle} = style || {};
18
+
19
+ return (
20
+ // Using transform translate to position overlay items will result in a smooth zooming
21
+ // effect, whereas using the top/left css properties will cause overlay items to
22
+ // jiggle when zooming
23
+ <div style={{transform: `translate(${x}px, ${y}px)`, position: 'absolute', zIndex}}>
24
+ <div style={{userSelect: 'none', ...remainingStyle}} {...props}>
25
+ {children}
26
+ </div>
27
+ </div>
28
+ );
29
+ }
30
+ }