# react-mapbox-gl-cluster

[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/thuanmb/react-mapbox-gl-cluster/blob/master/LICENSE)
[![npm downloads](https://img.shields.io/npm/dm/react-mapbox-gl-cluster.svg)](https://www.npmjs.com/package/react-mapbox-gl-cluster)

The `React` component for the cluster layer in the `mapbox-gl`.

The cluster layer has some build-in actions:

1.  Zoom in when clicking on a cluster.
2.  Show the spiderifiers when clicking on a cluster which contains the points at same location.

This layer must be rendered inside `react-mapbox-gl` component.

## Examples:

- https://github.com/thuanmb/react-mapbox-gl-cluster/blob/master/src/App.js

![Demo Cluster.](./demo/demo.gif)

Please note that the `ReactMapboxGlCluster` should be used together with the `React` wrapper of `mapbox-gl` e.g. `react-mapbox-gl`.
https://github.com/alex3165/react-mapbox-gl

```js
import React, {Component} from 'react';
import ReactMapboxGl from 'react-mapbox-gl';
import {ReactMapboxGlCluster} from 'react-mapbox-gl-cluster';
import {data} from './data';

const Map = ReactMapboxGl({
  accessToken: process.env.MAPBOX_GL_TOKEN,
});

const mapProps = {
  center: [-95.7129, 37.0902],
  zoom: [3],
  style: 'mapbox://styles/mapbox/streets-v8',
};

class App extends Component {
  getEventHandlers() {
    return {
      onClick: (properties, coords, offset) =>
        console.log(`Receive event onClick at properties: ${properties}, coords: ${coords}, offset: ${offset}`),
      onMouseEnter: (properties, coords, offset) =>
        console.log(`Receive event onMouseEnter at properties: ${properties}, coords: ${coords}, offset: ${offset}`),
      onMouseLeave: (properties, coords, offset) =>
        console.log(`Receive event onMouseLeave at properties: ${properties}, coords: ${coords}, offset: ${offset}`),
      onClusterClick: (properties, coords, offset) =>
        console.log(`Receive event onClusterClick at properties: ${properties}, coords: ${coords}, offset: ${offset}`),
    };
  }

  render() {
    return (
      <div className="App">
        <Map {...mapProps} onStyleLoad={this.onStyleLoad}>
          <ReactMapboxGlCluster data={data} {...this.getEventHandlers()} />
        </Map>
      </div>
    );
  }
}
```

## Documentations

#### Properties

- `data (object)`
  Data source for layer. It must to follow FeatureCollection geojson format

- `radius (number)`
  [Optional] Cluster radius, in pixels.

- `minZoom (number)`
  [Optional] Minimum zoom level at which clusters are generated.

- `maxZoom (number)`
  [Optional] Maximum zoom level at which clusters are generated.

- `extent (number)`
  [Optional](Tiles) Tile extent. Radius is calculated relative to this value.

- `nodeSize (number)`
  [Optional] Size of the KD-tree leaf node. Affects performance.

- `pointClassName (string)`
  [Optional] The class name of each point.

- `pointStyles (object)`
  [Optional] The class name of each cluster.

- `clusterClassName (string)`
  [Optional] The class name of each cluster.

- `spiralComponent (element)`
  [Optional] The custom component for the spiral. Example usage:
  ```
  const CustomSpiralComponent = ({properties, ...restProps}) => {
    const onClick = e => {
      console.log(`Receive event onClick in spiral at properties: ${JSON.stringify(properties)}`);
    };
    return <div className="spiderifier-marker-content" onClick={onClick}></div>;
  };

  ...
  <Map {...mapProps} onStyleLoad={this.onStyleLoad}>
    <ReactMapboxGlCluster data={data} {...this.getEventHandlers()} spiralComponent={CustomSpiralComponent} />
  </Map>
  ```

- `markerComponent (element)`
  [Optional] The custom component for marker. Example usage:
  ```
  const CustomeMarkerComponent = ({properties, className, cssObject}) => {
    const onClick = e => {
      console.log(`Receive event onClick in marker at properties: ${JSON.stringify(properties)}`);
    };
    return <div className={className} style={cssObject} onClick={onClick} />;
  };
  ...
  <Map {...mapProps} onStyleLoad={this.onStyleLoad}>
    <ReactMapboxGlCluster data={data} {...this.getEventHandlers()} markerComponent={CustomMarkerComponent} />
  </Map>
  ```

- clusterClickEnabled (bool)
  [Optional] Enable/disable zoom on cluster click

#### Events

- `onClick (function)`
  [Optional] Handler for when user on marker

- `onMouseEnter (function)`
  [Optional] Handle when user move the mouse enter a point

- `onMouseLeave (function)`
  [Optional] Handle when user move the mouse leave a point

- `onClusterClick (function)`
  [Optional] Handle when user click on cluster

- `onClusterMouseEnter (function)`
  [Optional] Handle when user move the mouse enter a cluster

- `onClusterMouseLeave (function)`
  [Optional] Handle when user move the mouse leave a cluster

## ChangeLog:

### 1.20.0

- Upgrading packages

### 1.19.0

- Upgrading packages

### 1.18.0

- Removing node-sass dependency

### 1.17.0

- Fix TS compiling does not

### 1.15.0

- Upgrading packages

### 1.14.0

- Upgrading packages

### 1.12.0

- Upgrading packages

### 1.11.0

- Support `clusterClickEnabled` flag to enable/disable on cluster click event
- Bumps depedencies version

### 1.10.0

- Use caret version for react-mapbox-gl

### 1.7.0

- Upgrading packages

### 1.6.0

- Upgrading packages

### 1.5.0

- Upgrading packages

### 1.4.0

- Upgrading packages

### 1.3.0

- Upgrading packages

### 1.2.1

- Fix bundling issue.

### 1.2.0

- Support custom marker.

  ```
  const CustomeMarkerComponent = ({properties, className, cssObject}) => {
    const onClick = e => {
      console.log(`Receive event onClick in marker at properties: ${JSON.stringify(properties)}`);
    };
    return <div className={className} style={cssObject} onClick={onClick} />;
  };
  ...
  <Map {...mapProps} onStyleLoad={this.onStyleLoad}>
    <ReactMapboxGlCluster data={data} {...this.getEventHandlers()} markerComponent={CustomMarkerComponent} />
  </Map>
  ```

### 1.1.0

- Support custom spiral.

  ```
  const CustomSpiralComponent = ({properties, ...restProps}) => {
    const onClick = e => {
      console.log(`Receive event onClick in spiral at properties: ${JSON.stringify(properties)}`);
    };
    return <div className="spiderifier-marker-content" onClick={onClick}></div>;
  };

  ...
  <Map {...mapProps} onStyleLoad={this.onStyleLoad}>
    <ReactMapboxGlCluster data={data} {...this.getEventHandlers()} spiralComponent={CustomSpiralComponent} />
  </Map>
  ```

### 1.0.0

- Upgrading depedencies to latest version.

### 0.2.0 [BREAKING CHANGES]

- Upgrade all packages to latest version. These packages include: `react`, `mapbox-gl`, `react-mapbox-gl`, `react-mapbox-gl-spiderifier`...

### 0.1.7

- Fix JS error when click on a marker

## Development

### Starting the server in local
- Adding the `.env` file
- Adding the key `REACT_APP_MAPBOX_GL_TOKEN` into the `.env` file
- Starting the server by: `yarn start`

## Upgrading dependencies checklist
- Upgrading the dependencies
```
yarn upgrade-interactive --latest
```
- Pull the latest code
- Create a new branch
- Test if the app works after upgrading: `yarn start`
- Build the package: `yarn build`
- Increasing the package version in the `package.json`
- Adding the release note in the `README`
- Push the change into Github
- Publish the package into npmjs: `npm publish`
