Skip to content

Commit

Permalink
test: setSelectedFeature method #29
Browse files Browse the repository at this point in the history
  • Loading branch information
wazolab committed Nov 20, 2024
1 parent 0982859 commit a9e79d7
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 44 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ map.on('load', () => {
})

// Create whatever HTML element you want as Cluster
const clusterRender = (element: HTMLDivElement, props: MapGeoJSONFeature['properties']): void => {}
const clusterRender = (element: HTMLDivElement, props: NonNullable<GeoJSON.GeoJsonProperties>): void => {}

// Create whatever HTML element you want as individual Marker
const markerRender = (element: HTMLDivElement, feature: MapGeoJSONFeature, markerSize: number): void => {}
const markerRender = (element: HTMLDivElement, feature: GeoJSON.Feature, markerSize: number): void => {}

// Create whatever HTML element you want as Pin Marker
const pinMarkerRender = (coords: LngLatLike, offset: Point): Marker => {}
Expand Down Expand Up @@ -117,12 +117,12 @@ Create a new Maplibre GL JS plugin for feature (cluster / individual marker) ren
|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|----------|-----------------------------------------------------|
| clusterMaxZoom | `number` | Maximal zoom level at which we force the rendering of the Unfolded Cluster || `17` |
| clusterMinZoom | `number` | Minimal zoom level at which we force the rendering of the Unfolded Cluster || `0` |
| clusterRenderFn | `(element: HTMLDivElement, props: MapGeoJSONFeature['properties']): void` | Cluster render function || `src/utils/helpers.ts/clusterRenderDefault()` |
| clusterRenderFn | `(element: HTMLDivElement, props: NonNullable<GeoJSON.GeoJsonProperties>): void` | Cluster render function || `src/utils/helpers.ts/clusterRenderDefault()` |
| fitBoundsOptions | [`FitBoundsOptions`](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/FitBoundsOptions) | Options for [Map#fitBounds](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#fitbounds) method || `{ padding: 20 }` |
| initialFeature | [`MapGeoJSONFeature`](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/MapGeoJSONFeature/) | Feature to select on initial rendering || `undefined` |
| markerRenderFn | `(element: HTMLDivElement, feature: MapGeoJSONFeature, markerSize: number): void` | Individual Marker render function || `src/utils/helpers.ts/markerRenderDefault()` |
| initialFeature | [`GeoJSON.Feature`](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/GeoJSON.Feature/) | Feature to select on initial rendering || `undefined` |
| markerRenderFn | `(element: HTMLDivElement, feature: GeoJSON.Feature, markerSize: number): void` | Individual Marker render function || `src/utils/helpers.ts/markerRenderDefault()` |
| markerSize | `number` (in px) | Size of Marker || `24` |
| unfoldedClusterRenderFn | `(parent: HTMLDivElement, items: MapGeoJSONFeature[], markerSize: number, renderMarker: (feature: MapGeoJSONFeature) => HTMLDivElement, clickHandler: (e: Event, feature: MapGeoJSONFeature) => void) => void` | Unfolded Cluster render function || `src/utils/helpers.ts/unfoldedClusterRenderSmart()` |
| unfoldedClusterRenderFn | `(parent: HTMLDivElement, items: GeoJSON.Feature[], markerSize: number, renderMarker: (feature: GeoJSON.Feature) => HTMLDivElement, clickHandler: (e: Event, feature: GeoJSON.Feature) => void) => void` | Unfolded Cluster render function || `src/utils/helpers.ts/unfoldedClusterRenderSmart()` |
| unfoldedClusterRenderSmart | Mix between Circular and HexaShape shape Unfolded Cluster render function | - | - | - |
| unfoldedClusterRenderGrid | Grid shape Unfolded Cluster render function function | - | - | - |
| unfoldedClusterRenderCircle | Circular shape Unfolded Cluster render function function | - | - | - |
Expand All @@ -133,10 +133,10 @@ Create a new Maplibre GL JS plugin for feature (cluster / individual marker) ren
#### Methods
| Name | Type | Description |
|----------------------|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| addEventListener | ('feature-click', (e: Event) => void) | Listen to feature click and return a [`MapGeoJSONFeature`](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/MapGeoJSONFeature/) from `e.detail.selectedFeature` for external control. |
| addEventListener | ('feature-click', (e: Event) => void) | Listen to feature click and return a [`GeoJSON.Feature`](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/GeoJSON.Feature/) from `e.detail.selectedFeature` for external control. |
| resetSelectedFeature | () => void | Remove selected feature and associated Pin Marker |
| setBoundsOptions | (options: [`FitBoundsOptions`](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/FitBoundsOptions)) => void | Update Map's visible area |
| setSelectedFeature | (feature: [`MapGeoJSONFeature`](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/MapGeoJSONFeature/)) => void | Set selected feature and display Pin Marker on top of it |
| setSelectedFeature | (feature: [`GeoJSON.Feature`](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/GeoJSON.Feature/)) => void | Set selected feature and display Pin Marker on top of it |

## Dev
Install dependencies
Expand Down
42 changes: 21 additions & 21 deletions src/teritorio-cluster.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FitBoundsOptions, GeoJSONSource, LngLatLike, MapGeoJSONFeature, MapSourceDataEvent } from 'maplibre-gl'
import type { FitBoundsOptions, GeoJSONSource, LngLatLike, MapSourceDataEvent } from 'maplibre-gl'
import { Marker, Point } from 'maplibre-gl'
import {
clusterRenderDefault,
Expand All @@ -13,22 +13,22 @@ import { featureCollection } from '@turf/helpers'
type UnfoldedCluster = (
(
parent: HTMLDivElement,
items: MapGeoJSONFeature[],
items: GeoJSON.Feature[],
markerSize: number,
renderMarker: (feature: MapGeoJSONFeature) => HTMLDivElement,
clickHandler: (e: Event, feature: MapGeoJSONFeature) => void
renderMarker: (feature: GeoJSON.Feature) => HTMLDivElement,
clickHandler: (e: Event, feature: GeoJSON.Feature) => void
) => void
)
type ClusterRender = (
(
element: HTMLDivElement,
props: MapGeoJSONFeature['properties']
props: NonNullable<GeoJSON.GeoJsonProperties>
) => void
)
type MarkerRender = (
(
element: HTMLDivElement,
feature: MapGeoJSONFeature,
feature: GeoJSON.Feature,
markerSize: number
) => void
)
Expand All @@ -38,20 +38,20 @@ type PinMarkerRender = (
offset: Point
) => Marker
)
type FeatureInClusterMatch = { clusterId: string, feature: MapGeoJSONFeature }
type FeatureMatch = FeatureInClusterMatch | MapGeoJSONFeature
type FeatureInClusterMatch = { clusterId: string, feature: GeoJSON.Feature }
type FeatureMatch = FeatureInClusterMatch | GeoJSON.Feature

const UnfoldedClusterClass = 'teritorio-unfolded-cluster'

export class TeritorioCluster extends EventTarget {
map: maplibregl.Map
clusterLeaves: Map<string, MapGeoJSONFeature[]>
clusterLeaves: Map<string, GeoJSON.Feature[]>
clusterMaxZoom: number
clusterMinZoom: number
clusterRender?: ClusterRender
featuresMap: Map<string, MapGeoJSONFeature>
featuresMap: Map<string, GeoJSON.Feature>
fitBoundsOptions: FitBoundsOptions
initialFeature?: MapGeoJSONFeature
initialFeature?: GeoJSON.Feature
markerRender?: MarkerRender
markerSize: number
markersOnScreen: Map<string, Marker>
Expand All @@ -72,7 +72,7 @@ export class TeritorioCluster extends EventTarget {
clusterMinZoom?: number,
clusterRenderFn?: ClusterRender,
fitBoundsOptions?: FitBoundsOptions,
initialFeature?: MapGeoJSONFeature,
initialFeature?: GeoJSON.Feature,
markerRenderFn?: MarkerRender,
markerSize?: number
unfoldedClusterRenderFn?: UnfoldedCluster,
Expand All @@ -83,11 +83,11 @@ export class TeritorioCluster extends EventTarget {
super()

this.map = map
this.clusterLeaves = new Map<string, MapGeoJSONFeature[]>()
this.clusterLeaves = new Map<string, GeoJSON.Feature[]>()
this.clusterMaxZoom = options?.clusterMaxZoom || 17
this.clusterMinZoom = options?.clusterMinZoom || 0
this.clusterRender = options?.clusterRenderFn
this.featuresMap = new Map<string, MapGeoJSONFeature>()
this.featuresMap = new Map<string, GeoJSON.Feature>()
this.fitBoundsOptions = options?.fitBoundsOptions || { padding: 20 }
this.initialFeature = options?.initialFeature
this.markerRender = options?.markerRenderFn
Expand Down Expand Up @@ -180,7 +180,7 @@ export class TeritorioCluster extends EventTarget {
return new Point(x - clusterXCenter + (width / 2), y - clusterYCenter + (height / 2))
}

#featureClickHandler = (e: Event, feature: MapGeoJSONFeature) => {
#featureClickHandler = (e: Event, feature: GeoJSON.Feature) => {
e.stopPropagation()

if (!(e.currentTarget instanceof HTMLElement) || this.selectedFeatureId === getFeatureId(feature))
Expand Down Expand Up @@ -211,7 +211,7 @@ export class TeritorioCluster extends EventTarget {
}
}

#fitBoundsToClusterLeaves = (features: MapGeoJSONFeature[]) => {
#fitBoundsToClusterLeaves = (features: GeoJSON.Feature[]) => {
const bounds = bbox(featureCollection(features))

this.map.fitBounds(bounds as [number, number, number, number], this.fitBoundsOptions)
Expand Down Expand Up @@ -254,7 +254,7 @@ export class TeritorioCluster extends EventTarget {
this.ticking = true
}

#renderCluster = (id: string, props: MapGeoJSONFeature['properties']) => {
#renderCluster = (id: string, props: NonNullable<GeoJSON.GeoJsonProperties>) => {
const element = document.createElement('div')
element.id = id

Expand All @@ -278,7 +278,7 @@ export class TeritorioCluster extends EventTarget {
return element
}

#renderMarker = (feature: MapGeoJSONFeature) => {
#renderMarker = (feature: GeoJSON.Feature) => {
const element = document.createElement('div')
element.id = getFeatureId(feature)

Expand All @@ -297,7 +297,7 @@ export class TeritorioCluster extends EventTarget {
this.pinMarker.addTo(this.map)
}

#renderUnfoldedCluster = (id: string, leaves: MapGeoJSONFeature[]) => {
#renderUnfoldedCluster = (id: string, leaves: GeoJSON.Feature[]) => {
const element = document.createElement('div')
element.id = id
element.classList.add(UnfoldedClusterClass)
Expand Down Expand Up @@ -348,7 +348,7 @@ export class TeritorioCluster extends EventTarget {
// Get cluster's leaves
if (feature.properties.cluster) {
const source = this.map.getSource(this.sourceId) as GeoJSONSource
const leaves = await source.getClusterLeaves(Number.parseInt(id), feature.properties.point_count, 0) as MapGeoJSONFeature[]
const leaves = await source.getClusterLeaves(Number.parseInt(id), feature.properties.point_count, 0) as GeoJSON.Feature[]
this.clusterLeaves.set(id, leaves)
}
}
Expand All @@ -365,7 +365,7 @@ export class TeritorioCluster extends EventTarget {
let marker = this.markersOnScreen.get(id)
const props = feature.properties

if (props.cluster) {
if (props?.cluster) {
const leaves = this.clusterLeaves.get(id)

if (!leaves) {
Expand Down
28 changes: 14 additions & 14 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { LngLatLike, MapGeoJSONFeature } from 'maplibre-gl'
import type { LngLatLike } from 'maplibre-gl'
import { Marker, Point } from 'maplibre-gl'

// Helper to apply styles on DOM element
Expand All @@ -12,10 +12,10 @@ export const buildCss = (htmlEl: HTMLElement, styles: { [key: string]: string })
// Circle shape
export const unfoldedClusterRenderCircle = (
parent: HTMLDivElement,
items: MapGeoJSONFeature[],
items: GeoJSON.Feature[],
markerSize: number,
renderMarker: (feature: MapGeoJSONFeature) => HTMLDivElement,
clickHandler: (e: Event, feature: MapGeoJSONFeature) => void
renderMarker: (feature: GeoJSON.Feature) => HTMLDivElement,
clickHandler: (e: Event, feature: GeoJSON.Feature) => void
): void => {
const radius = (markerSize / 2) / Math.sin(Math.PI / items.length)
let angle = 360 / items.length
Expand Down Expand Up @@ -47,10 +47,10 @@ export const unfoldedClusterRenderCircle = (
// HexaGrid shape
export const unfoldedClusterRenderHexaGrid = (
parent: HTMLDivElement,
items: MapGeoJSONFeature[],
items: GeoJSON.Feature[],
markerSize: number,
renderMarker: (feature: MapGeoJSONFeature) => HTMLDivElement,
clickHandler: (e: Event, feature: MapGeoJSONFeature) => void
renderMarker: (feature: GeoJSON.Feature) => HTMLDivElement,
clickHandler: (e: Event, feature: GeoJSON.Feature) => void
): void => {
const radius = (markerSize / 2) / Math.sin(Math.PI / items.length)

Expand Down Expand Up @@ -96,10 +96,10 @@ export const unfoldedClusterRenderHexaGrid = (
// Smart: mix between Circle and HexaGrid shape
export const unfoldedClusterRenderSmart = (
parent: HTMLDivElement,
items: MapGeoJSONFeature[],
items: GeoJSON.Feature[],
markerSize: number,
renderMarker: (feature: MapGeoJSONFeature) => HTMLDivElement,
clickHandler: (e: Event, feature: MapGeoJSONFeature) => void
renderMarker: (feature: GeoJSON.Feature) => HTMLDivElement,
clickHandler: (e: Event, feature: GeoJSON.Feature) => void
): void => {
if (items.length <= 5) {
unfoldedClusterRenderCircle(parent, items, markerSize, renderMarker, clickHandler)
Expand All @@ -111,10 +111,10 @@ export const unfoldedClusterRenderSmart = (
// Grid shape
export const unfoldedClusterRenderGrid = (
parent: HTMLDivElement,
items: MapGeoJSONFeature[],
items: GeoJSON.Feature[],
_markerSize: number,
renderMarker: (feature: MapGeoJSONFeature) => HTMLDivElement,
clickHandler: (e: Event, feature: MapGeoJSONFeature) => void
renderMarker: (feature: GeoJSON.Feature) => HTMLDivElement,
clickHandler: (e: Event, feature: GeoJSON.Feature) => void
): void => {
buildCss(parent, {
'display': 'flex',
Expand All @@ -136,7 +136,7 @@ export const unfoldedClusterRenderGrid = (
// Cluster default styles
export const clusterRenderDefault = (
element: HTMLDivElement,
props: MapGeoJSONFeature['properties']
props: NonNullable<GeoJSON.GeoJsonProperties>
): void => {
element.innerHTML = props.point_count

Expand Down
11 changes: 10 additions & 1 deletion tests/mocks/maplibre-gl.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ vi.mock('maplibre-gl', () => {
getClusterLeaves: vi.fn(),
}),
fitBounds: vi.fn(),
}))
})),
Marker: vi.fn().mockImplementation(() => ({
setLngLat: vi.fn().mockReturnThis(),
addTo: vi.fn().mockReturnThis(),
setOffset: vi.fn().mockReturnThis(),
})),
Point: vi.fn().mockImplementation((x, y) => ({
x,
y,
})),
}
})
Loading

0 comments on commit a9e79d7

Please sign in to comment.