├── .eslintrc.js
├── .gitignore
├── .prettierrc.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── examples
├── custom-marker-cluster
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── index.d.ts
│ │ ├── index.tsx
│ │ ├── location.svg
│ │ └── react-app-env.d.ts
│ └── tsconfig.json
├── ten-thousand-marker
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── react-app-env.d.ts
│ │ ├── realworld.js
│ │ ├── reportWebVitals.ts
│ │ └── setupTests.ts
│ ├── test.tsx
│ └── tsconfig.json
└── vite-example
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public
│ └── vite.svg
│ ├── src
│ ├── App.tsx
│ ├── main.tsx
│ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ ├── vite.config.ts
│ └── vite.svg
├── package-lock.json
├── package.json
├── showcase.gif
├── src
├── assets
│ ├── MarkerCluster.Default.css
│ ├── MarkerCluster.css
│ ├── marker-icon-2x.png
│ ├── marker-icon.png
│ └── marker-shadow.png
└── index.tsx
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser
3 | parserOptions: {
4 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
5 | sourceType: 'module', // Allows for the use of imports
6 | ecmaFeatures: {
7 | jsx: true, // Allows for the parsing of JSX
8 | },
9 | },
10 | settings: {
11 | react: {
12 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
13 | },
14 | },
15 | extends: [
16 | 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react
17 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
18 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
19 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
20 | ],
21 | plugins: ['react-hooks'],
22 | rules: {
23 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
24 | // e.g. "@typescript-eslint/explicit-function-return-type": "off",
25 | 'react-hooks/rules-of-hooks': 'error',
26 | 'react-hooks/exhaustive-deps': 'warn',
27 | },
28 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /demo/dist
3 | /es
4 | /lib
5 | /node_modules
6 | /umd
7 | /examples/custom-marker-cluster/.yalc
8 | /examples/ten-thousand-marker/.yalc
9 | .yalc
10 | npm-debug.log*
11 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | semi: false
2 | trailingComma: 'all'
3 | singleQuote: true
4 | printWidth: 100
5 | tabWidth: 2
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Prerequisites
2 |
3 | [Node.js](http://nodejs.org/) >= 10 must be installed.
4 |
5 | ## Installation
6 |
7 | - Running `npm install` in the component's root directory will install everything you need for development.
8 |
9 | ## Demo Development Server
10 |
11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading.
12 |
13 | ## Running Tests
14 |
15 | - `npm test` will run the tests once.
16 |
17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`.
18 |
19 | - `npm run test:watch` will run the tests on every change.
20 |
21 | ## Building
22 |
23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app.
24 |
25 | - `npm run clean` will delete built resources.
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 A. Kürşat Uzun
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-leaflet-cluster [](https://www.npmjs.com/package/react-leaflet-cluster)
2 |
3 | - [x] React-leaflet v4 support
4 | - [x] Typescript support
5 |
6 | React-leaflet-cluster is a plugin for react-leaflet. A wrapper component of Leaflet.markercluster. Ready to be integrated into your React.js application to create beautifully animated Marker Clustering functionality.
7 |
8 | 
9 |
10 | ### Examples - Code Sandbox
11 | * [10.000 marker](https://codesandbox.io/s/hidden-breeze-nrd3e?fontsize=14&hidenavigation=1&theme=dark)
12 | * [Custom marker cluster](https://codesandbox.io/s/beautiful-pike-j2l0w?file=/src/App.tsx)
13 |
14 | ### Installation
15 | `yarn add react-leaflet-cluster`
16 |
17 | Or with npm:
18 | `npm i react-leaflet-cluster`
19 |
20 |
21 | #### Prerequisites
22 | Make sure that you've installed react-leaflet and leaflet.
23 | ```json
24 | "react": "18.x",
25 | "leaflet": "1.8.x",
26 | "react-leaflet": "4.0.x"
27 | ```
28 |
29 | #### API
30 | For more detailed guide and API see:
31 | https://akursat.gitbook.io/marker-cluster/api
32 |
33 | #### Usage
34 |
35 | ```tsx
36 | import MarkerClusterGroup from 'react-leaflet-cluster'
37 | import {MapContainer, Marker } from 'react-leaflet'
38 | import 'leaflet/dist/leaflet.css'
39 | import {addressPoints} from './realworld'
40 |
41 | const Demo = () => {
42 | return (
43 |
49 |
52 | {(addressPoints as AdressPoint).map((address, index) => (
53 |
59 | ))}
60 |
61 |
62 | )
63 | }
64 | ```
65 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Code Sandbox
3 |
4 | https://codesandbox.io/s/beautiful-pike-j2l0w?file=/src/App.tsx
5 |
6 | ## Available Scripts
7 |
8 | In the project directory, you can run:
9 |
10 | ### `yarn start`
11 |
12 | Runs the app in the development mode.\
13 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
14 |
15 | The page will reload if you make edits.\
16 | You will also see any lint errors in the console.
17 |
18 | ### `yarn test`
19 |
20 | Launches the test runner in the interactive watch mode.\
21 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
22 |
23 | ### `yarn build`
24 |
25 | Builds the app for production to the `build` folder.\
26 | It correctly bundles React in production mode and optimizes the build for the best performance.
27 |
28 | The build is minified and the filenames include the hashes.\
29 | Your app is ready to be deployed!
30 |
31 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
32 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "custom-marker-cluster",
3 | "version": "1.0.3",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "@types/jest": "^26.0.15",
10 | "@types/leaflet": "^1.9.0",
11 | "@types/leaflet.markercluster": "^1.4.3",
12 | "@types/node": "^12.0.0",
13 | "@types/react": "^18.0.0",
14 | "@types/react-dom": "^18.0.0",
15 | "leaflet": "^1.9.0",
16 | "react": "^18.0.0",
17 | "react-dom": "^18.0.0",
18 | "react-leaflet": "^4.1.0",
19 | "react-leaflet-cluster": "^2.1.0",
20 | "react-scripts": "4.0.2",
21 | "typescript": "^4.1.2",
22 | "web-vitals": "^1.0.1"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/examples/custom-marker-cluster/public/favicon.ico
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/examples/custom-marker-cluster/public/logo192.png
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Custom Marker Cluster",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | }
15 | ],
16 | "start_url": ".",
17 | "display": "standalone",
18 | "theme_color": "#000000",
19 | "background_color": "#ffffff"
20 | }
21 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/src/App.css:
--------------------------------------------------------------------------------
1 | .mycluster {
2 | width: 40px;
3 | height: 40px;
4 | background-color: greenyellow;
5 | text-align: center;
6 | font-size: 24px;
7 | }
8 |
9 | .custom-marker-cluster{
10 | background: #ffffff;
11 | border: 15px solid #f00800;
12 | border-radius: 50%;
13 | color: #000000;
14 | height: 33px;
15 | line-height: 37px;
16 | text-align: center;
17 | width: 33px;
18 | }
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { MapContainer, Marker, TileLayer } from 'react-leaflet'
3 | import L, { MarkerCluster } from 'leaflet'
4 | import MarkerClusterGroup from 'react-leaflet-cluster'
5 | import './App.css'
6 | import 'leaflet/dist/leaflet.css'
7 |
8 | const customIcon = new L.Icon({
9 | iconUrl: require('./location.svg').default,
10 | iconSize: new L.Point(40, 47),
11 | })
12 |
13 | const createClusterCustomIcon = function (cluster: MarkerCluster) {
14 | return new L.DivIcon({
15 | html: `${cluster.getChildCount()}`,
16 | className: 'custom-marker-cluster',
17 | iconSize: L.point(33, 33, true),
18 | })
19 | }
20 |
21 | function App() {
22 | const [dynamicPosition, setPosition] = useState([41.051687, 28.987261])
23 |
24 | return (
25 |
26 |
Custom Marker Cluster
27 |
34 |
40 |
44 | console.log('onClick', e)}
46 | iconCreateFunction={createClusterCustomIcon}
47 | maxClusterRadius={150}
48 | spiderfyOnMaxZoom={true}
49 | polygonOptions={{
50 | fillColor: '#ffffff',
51 | color: '#f00800',
52 | weight: 5,
53 | opacity: 1,
54 | fillOpacity: 0.8,
55 | }}
56 | showCoverageOnHover={true}
57 | >
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | export default App
71 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import L, { LeafletMouseEventHandlerFn } from 'leaflet';
3 | import 'leaflet.markercluster';
4 | import './assets/MarkerCluster.css';
5 | import './assets/MarkerCluster.Default.css';
6 | declare type ClusterEvents = {
7 | onClick?: LeafletMouseEventHandlerFn;
8 | onDblClick?: LeafletMouseEventHandlerFn;
9 | onMouseDown?: LeafletMouseEventHandlerFn;
10 | onMouseUp?: LeafletMouseEventHandlerFn;
11 | onMouseOver?: LeafletMouseEventHandlerFn;
12 | onMouseOut?: LeafletMouseEventHandlerFn;
13 | onContextMenu?: LeafletMouseEventHandlerFn;
14 | };
15 | declare const MarkerClusterGroup: React.ForwardRefExoticComponent>;
18 | export default MarkerClusterGroup;
19 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById('root'),
10 | )
11 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/src/location.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
36 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/custom-marker-cluster/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Code Sandbox
3 | https://codesandbox.io/s/hidden-breeze-nrd3e?fontsize=14&hidenavigation=1&theme=dark
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `yarn start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `yarn test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `yarn build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ten-thousand-marker",
3 | "version": "1.0.3",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "@types/jest": "^26.0.15",
10 | "@types/leaflet": "^1.9.0",
11 | "@types/leaflet.markercluster": "^1.4.3",
12 | "@types/node": "^12.0.0",
13 | "@types/react": "^18.0.0",
14 | "@types/react-dom": "^18.0.0",
15 | "leaflet": "^1.9.0",
16 | "react": "^18.0.0",
17 | "react-dom": "^18.0.0",
18 | "react-leaflet": "^4.1.0",
19 | "react-leaflet-cluster": "^2.1.0",
20 | "react-scripts": "4.0.2",
21 | "typescript": "^4.1.2",
22 | "web-vitals": "^1.0.1"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/examples/ten-thousand-marker/public/favicon.ico
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/examples/ten-thousand-marker/public/logo192.png
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Ten Thousand Marker",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | }
15 | ],
16 | "start_url": ".",
17 | "display": "standalone",
18 | "theme_color": "#000000",
19 | "background_color": "#ffffff"
20 | }
21 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { MapContainer, Marker, TileLayer } from 'react-leaflet'
3 | import { addressPoints } from './realworld'
4 | import 'leaflet/dist/leaflet.css'
5 | import MarkerClusterGroup from "react-leaflet-cluster";
6 | type AdressPoint = Array<[number, number, string]>
7 |
8 | function App() {
9 | return (
10 |
11 |
10.000 marker
12 |
18 |
22 |
23 | {(addressPoints as AdressPoint).map((address, index) => (
24 |
25 | ))}
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export default App;
33 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/test.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/examples/ten-thousand-marker/test.tsx
--------------------------------------------------------------------------------
/examples/ten-thousand-marker/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/examples/vite-example/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: { browser: true, es2020: true },
3 | extends: [
4 | 'eslint:recommended',
5 | 'plugin:@typescript-eslint/recommended',
6 | 'plugin:react-hooks/recommended',
7 | ],
8 | parser: '@typescript-eslint/parser',
9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
10 | plugins: ['react-refresh'],
11 | rules: {
12 | 'react-refresh/only-export-components': 'warn',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/examples/vite-example/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/vite-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/vite-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-vite-app",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "leaflet": "^1.9.4",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-leaflet": "^4.2.1",
17 | "react-leaflet-cluster": "^2.1.0"
18 | },
19 | "devDependencies": {
20 | "@types/leaflet": "^1.9.3",
21 | "@types/react": "^18.0.37",
22 | "@types/react-dom": "^18.0.11",
23 | "@typescript-eslint/eslint-plugin": "^5.59.0",
24 | "@typescript-eslint/parser": "^5.59.0",
25 | "@vitejs/plugin-react": "^4.0.0",
26 | "eslint": "^8.38.0",
27 | "eslint-plugin-react-hooks": "^4.6.0",
28 | "eslint-plugin-react-refresh": "^0.3.4",
29 | "typescript": "^5.0.2",
30 | "vite": "^4.3.9"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/vite-example/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/vite-example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { MapContainer, Marker, TileLayer } from "react-leaflet";
3 | import MarkerClusterGroup from "react-leaflet-cluster";
4 | import "leaflet/dist/leaflet.css";
5 |
6 | function App() {
7 | const [count, setCount] = useState(0);
8 |
9 | return (
10 |
11 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
Vite + React Leaflet Cluster
29 |
30 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default App;
39 |
--------------------------------------------------------------------------------
/examples/vite-example/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.tsx'
4 |
5 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/examples/vite-example/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/vite-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/examples/vite-example/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/vite-example/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/examples/vite-example/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-leaflet-cluster",
3 | "version": "2.1.0",
4 | "description": "React-leaflet-cluster is a plugin for react-leaflet. A wrapper component of Leaflet.markercluster.",
5 | "main": "lib/index.js",
6 | "types": "lib/index.d.ts",
7 | "files": [
8 | "css",
9 | "es",
10 | "lib",
11 | "umd"
12 | ],
13 | "scripts": {
14 | "build": "tsc && npm run minify && npm run copy:assets",
15 | "minify": "uglifyjs --compress --mangle --output lib/index.min.js -- lib/index.js",
16 | "format": "prettier --write \"src/**/*.tsx\"",
17 | "lint": "tslint -p tsconfig.json",
18 | "prepublishOnly": "npm test && npm run lint",
19 | "preversion": "npm run lint",
20 | "version": "npm run format && git add -A src",
21 | "postversion": "git push && git push --tags",
22 | "copy:assets": "cpx 'src/assets/**' 'lib/assets'"
23 | },
24 | "dependencies": {
25 | "leaflet.markercluster": "^1.5.3"
26 | },
27 | "peerDependencies": {
28 | "leaflet": "^1.8.0",
29 | "react": "^18.0.0",
30 | "react-dom": "^18.0.0",
31 | "react-leaflet": "^4.0.0"
32 | },
33 | "devDependencies": {
34 | "@types/leaflet": "^1.7.11",
35 | "@types/leaflet.markercluster": "^1.5.0",
36 | "@types/node": "^14.18.21",
37 | "@types/react": "^18.0.0",
38 | "@types/react-dom": "^18.0.0",
39 | "@typescript-eslint/eslint-plugin": "^4.33.0",
40 | "@typescript-eslint/parser": "^4.33.0",
41 | "cpx": "^1.5.0",
42 | "eslint": "^7.32.0",
43 | "eslint-loader": "^4.0.2",
44 | "eslint-plugin-prettier": "^3.4.1",
45 | "eslint-plugin-react": "^7.30.0",
46 | "eslint-plugin-react-hooks": "^4.5.0",
47 | "leaflet": "^1.8.0",
48 | "prettier": "^2.6.2",
49 | "react": "^18.0.0",
50 | "react-dom": "^18.0.0",
51 | "react-leaflet": "^4.0.0",
52 | "ts-loader": "^8.4.0",
53 | "tslint": "^6.1.3",
54 | "tslint-config-prettier": "^1.18.0",
55 | "typescript": "^4.7.3",
56 | "uglify-js": "^3.16.0"
57 | },
58 | "author": "akursat",
59 | "homepage": "https://akursat.gitbook.io/marker-cluster/",
60 | "license": "SEE LICENSE IN ",
61 | "repository": "https://github.com/akursat/react-leaflet-cluster",
62 | "keywords": [
63 | "react",
64 | "leaflet",
65 | "marker-cluster",
66 | "cluster",
67 | "map",
68 | "react-leaflet-v4"
69 | ]
70 | }
71 |
--------------------------------------------------------------------------------
/showcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/showcase.gif
--------------------------------------------------------------------------------
/src/assets/MarkerCluster.Default.css:
--------------------------------------------------------------------------------
1 | /* To solve Next.js issues source from https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.Default.css */
2 | .marker-cluster-small {
3 | background-color: rgba(181, 226, 140, 0.6);
4 | }
5 | .marker-cluster-small div {
6 | background-color: rgba(110, 204, 57, 0.6);
7 | }
8 |
9 | .marker-cluster-medium {
10 | background-color: rgba(241, 211, 87, 0.6);
11 | }
12 | .marker-cluster-medium div {
13 | background-color: rgba(240, 194, 12, 0.6);
14 | }
15 |
16 | .marker-cluster-large {
17 | background-color: rgba(253, 156, 115, 0.6);
18 | }
19 | .marker-cluster-large div {
20 | background-color: rgba(241, 128, 23, 0.6);
21 | }
22 |
23 | /* IE 6-8 fallback colors */
24 | .leaflet-oldie .marker-cluster-small {
25 | background-color: rgb(181, 226, 140);
26 | }
27 | .leaflet-oldie .marker-cluster-small div {
28 | background-color: rgb(110, 204, 57);
29 | }
30 |
31 | .leaflet-oldie .marker-cluster-medium {
32 | background-color: rgb(241, 211, 87);
33 | }
34 | .leaflet-oldie .marker-cluster-medium div {
35 | background-color: rgb(240, 194, 12);
36 | }
37 |
38 | .leaflet-oldie .marker-cluster-large {
39 | background-color: rgb(253, 156, 115);
40 | }
41 | .leaflet-oldie .marker-cluster-large div {
42 | background-color: rgb(241, 128, 23);
43 | }
44 |
45 | .marker-cluster {
46 | background-clip: padding-box;
47 | border-radius: 20px;
48 | }
49 | .marker-cluster div {
50 | width: 30px;
51 | height: 30px;
52 | margin-left: 5px;
53 | margin-top: 5px;
54 |
55 | text-align: center;
56 | border-radius: 15px;
57 | font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
58 | }
59 | .marker-cluster span {
60 | line-height: 30px;
61 | }
--------------------------------------------------------------------------------
/src/assets/MarkerCluster.css:
--------------------------------------------------------------------------------
1 | /* To solve Next.js issues source from https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.css */
2 | .leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
3 | -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
4 | -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
5 | -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
6 | transition: transform 0.3s ease-out, opacity 0.3s ease-in;
7 | }
8 |
9 | .leaflet-cluster-spider-leg {
10 | /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
11 | -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
12 | -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
13 | -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
14 | transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
15 | }
--------------------------------------------------------------------------------
/src/assets/marker-icon-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/src/assets/marker-icon-2x.png
--------------------------------------------------------------------------------
/src/assets/marker-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/src/assets/marker-icon.png
--------------------------------------------------------------------------------
/src/assets/marker-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akursat/react-leaflet-cluster/9275f0ca060f7299bd0fa4cb0cc21aee94a669cf/src/assets/marker-shadow.png
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | extendContext,
4 | createElementObject,
5 | createPathComponent,
6 | LeafletContextInterface,
7 | } from '@react-leaflet/core'
8 | import L, { LeafletMouseEventHandlerFn } from 'leaflet'
9 | import 'leaflet.markercluster'
10 | import './assets/MarkerCluster.css'
11 | import './assets/MarkerCluster.Default.css'
12 |
13 | delete (L.Icon.Default as any).prototype._getIconUrl
14 | L.Icon.Default.mergeOptions({
15 | iconRetinaUrl: require('./assets/marker-icon-2x.png').default,
16 | iconUrl: require('./assets/marker-icon.png').default,
17 | shadowUrl: require('./assets/marker-shadow.png').default,
18 | })
19 |
20 | type ClusterType = { [key in string]: any }
21 |
22 | type ClusterEvents = {
23 | onClick?: LeafletMouseEventHandlerFn
24 | onDblClick?: LeafletMouseEventHandlerFn
25 | onMouseDown?: LeafletMouseEventHandlerFn
26 | onMouseUp?: LeafletMouseEventHandlerFn
27 | onMouseOver?: LeafletMouseEventHandlerFn
28 | onMouseOut?: LeafletMouseEventHandlerFn
29 | onContextMenu?: LeafletMouseEventHandlerFn
30 | }
31 |
32 | type MarkerClusterControl = L.MarkerClusterGroupOptions & {
33 | children: React.ReactNode
34 | } & ClusterEvents
35 |
36 | function getPropsAndEvents(props: MarkerClusterControl) {
37 | let clusterProps: ClusterType = {}
38 | let clusterEvents: ClusterType = {}
39 | const { children, ...rest } = props
40 | // Splitting props and events to different objects
41 | Object.entries(rest).forEach(([propName, prop]) => {
42 | if (propName.startsWith('on')) {
43 | clusterEvents = { ...clusterEvents, [propName]: prop }
44 | } else {
45 | clusterProps = { ...clusterProps, [propName]: prop }
46 | }
47 | })
48 | return { clusterProps, clusterEvents }
49 | }
50 |
51 | function createMarkerClusterGroup(props: MarkerClusterControl, context: LeafletContextInterface) {
52 | const { clusterProps, clusterEvents } = getPropsAndEvents(props)
53 | const markerClusterGroup = new L.MarkerClusterGroup(clusterProps)
54 | Object.entries(clusterEvents).forEach(([eventAsProp, callback]) => {
55 | const clusterEvent = `cluster${eventAsProp.substring(2).toLowerCase()}`
56 | markerClusterGroup.on(clusterEvent, callback)
57 | })
58 | return createElementObject(
59 | markerClusterGroup,
60 | extendContext(context, { layerContainer: markerClusterGroup }),
61 | )
62 | }
63 |
64 | const updateMarkerCluster = (
65 | instance: L.MarkerClusterGroup,
66 | props: MarkerClusterControl,
67 | prevProps: MarkerClusterControl,
68 | ) => {
69 | //TODO when prop change update instance
70 | // if (props. !== prevProps.center || props.size !== prevProps.size) {
71 | // instance.setBounds(getBounds(props))
72 | // }
73 | }
74 |
75 | const MarkerClusterGroup = createPathComponent(
76 | createMarkerClusterGroup,
77 | updateMarkerCluster,
78 | )
79 |
80 | export default MarkerClusterGroup
81 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "declaration": true,
6 | "outDir": "./lib",
7 | "strict": true,
8 | "jsx": "react",
9 | "esModuleInterop": true
10 | },
11 | "include": ["src"],
12 | "exclude": ["node_modules", "**/__tests__/*"]
13 | }
--------------------------------------------------------------------------------