├── .babelrc
├── .gitignore
├── README.md
├── index.html
├── index.js
├── package.json
├── src
├── ClusterMap.js
├── GeoJsonMap.js
├── Map.js
├── Pin.js
├── Pins.js
└── map-widget.css
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react",
5 | "stage-0"
6 | ],
7 | "plugins": [
8 | "autobind-class-methods",
9 | [
10 | "babel-plugin-styled-components",
11 | {
12 | "preprocess": true
13 | }
14 | ]
15 | ],
16 | "retainLines": true
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | .idea
3 | .vscode
4 |
5 | # Types
6 | typings
7 | typings.json
8 |
9 | # Environment
10 | node_modules
11 | gift_registry_hbc.env
12 | gr_api.env
13 | gr_client.env
14 | **/package-lock.json
15 | **/yarn.lock
16 |
17 | # Translations
18 | frontend/src/system/translations/*/*_old.json
19 | backend/api/translations/*/*_old.json
20 | updateTranslations/temp
21 |
22 | # Logs
23 | error.log
24 | logs/*
25 |
26 | # Docs
27 | /docs/dist/*
28 | orders.js
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #### Use this command to start:
2 | ```
3 | npm run start-dev
4 | ```
5 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React map with Leaflet
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Map from './src/Map';
4 | import ClusterMap from './src/ClusterMap';
5 | import GeoJsonMap from './src/GeoJsonMap';
6 |
7 | ReactDOM.render(, document.getElementById('map'));
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-map-with-leaflet",
3 | "version": "1.0.0",
4 | "description": "React map with Leaflet",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon server.js",
8 | "start-dev": "webpack-dev-server --open",
9 | "build": "webpack -p",
10 | "watch": "webpack --watch",
11 | "storybook": "start-storybook -p 6006",
12 | "build-storybook": "build-storybook"
13 | },
14 | "keywords": [
15 | "react",
16 | "map",
17 | "leaflet"
18 | ],
19 | "author": "Eugene Belkovich",
20 | "license": "MIT",
21 | "devDependencies": {
22 | "babel-core": "^6.26.3",
23 | "babel-loader": "^7.1.5",
24 | "babel-plugin-autobind-class-methods": "^5.0.1",
25 | "babel-plugin-styled-components": "^1.5.1",
26 | "babel-preset-es2015": "^6.24.1",
27 | "babel-preset-react": "^6.24.1",
28 | "babel-preset-stage-0": "^6.24.1",
29 | "babel-runtime": "^6.26.0",
30 | "css-loader": "^0.28.7",
31 | "html-loader": "^0.5.1",
32 | "html-webpack-plugin": "^2.30.1",
33 | "style-loader": "^0.19.1",
34 | "webpack": "^3.12.0",
35 | "webpack-dev-server": "^2.9.7"
36 | },
37 | "dependencies": {
38 | "geojson-world-map": "^0.0.2",
39 | "leaflet": "^1.3.1",
40 | "leaflet.markercluster": "^1.3.0",
41 | "react": "^16.5.2",
42 | "react-dom": "^16.5.2",
43 | "react-leaflet": "^1.9.1",
44 | "react-leaflet-markercluster": "^1.1.8"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/ClusterMap.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Map as LeafletMap, GeoJSON } from 'react-leaflet'
3 | import Pins from './Pins.js';
4 | import worldGeoJSON from 'geojson-world-map';
5 |
6 | class MapWidget extends Component {
7 | constructor(props) {
8 | super(props)
9 | let pins = []
10 | for (let i = 0; i < 5000; i++) {
11 | pins.push([Math.floor(Math.random() * 50), Math.floor(Math.random() * 50)]);
12 | }
13 | this.state = {
14 | pins
15 | }
16 | }
17 |
18 |
19 | render() {
20 |
21 | const pins = this.state.pins
22 | return (
23 |
35 | ({
38 | color: '#4a83ec',
39 | weight: 0.5,
40 | fillColor: "#1a1d62",
41 | fillOpacity: 1,
42 | })}
43 | />
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default MapWidget;
51 |
--------------------------------------------------------------------------------
/src/GeoJsonMap.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Map as LeafletMap, GeoJSON, Marker, Popup } from 'react-leaflet';
3 | import worldGeoJSON from 'geojson-world-map';
4 |
5 | class GeoJsonMap extends React.Component {
6 | render() {
7 | return (
8 |
20 | ({
23 | color: '#4a83ec',
24 | weight: 0.5,
25 | fillColor: "#1a1d62",
26 | fillOpacity: 1,
27 | })}
28 | />
29 |
30 |
31 | Popup for any custom information.
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | export default GeoJsonMap
40 |
--------------------------------------------------------------------------------
/src/Map.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Map as LeafletMap, TileLayer, Marker, Popup } from 'react-leaflet';
3 |
4 | class Map extends React.Component {
5 | render() {
6 | return (
7 |
19 |
22 |
23 |
24 | Popup for any custom information.
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
32 | export default Map
33 |
--------------------------------------------------------------------------------
/src/Pin.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { CircleMarker } from 'react-leaflet';
3 | import './map-widget.css';
4 |
5 | class Pin extends React.Component {
6 | render() {
7 | return (
8 |
15 | );
16 | }
17 | }
18 |
19 | export default Pin;
20 |
--------------------------------------------------------------------------------
/src/Pins.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import L from 'leaflet';
3 | import MarkerClusterGroup from 'react-leaflet-markercluster';
4 | import 'react-leaflet-markercluster/dist/styles.min.css';
5 | import Pin from './Pin.js';
6 | import './map-widget.css';
7 |
8 | const hexToRgb = (hex, opacity) => {
9 | if (!hex.startsWith('#')) return hex;
10 | const hashless = hex.slice(1);
11 | const num = parseInt(
12 | hashless.length === 3
13 | ? hashless.split('').map(c => c.repeat(2)).join('')
14 | : hashless,
15 | 16,
16 | );
17 | const red = num >> 16;
18 | const green = (num >> 8) & 255;
19 | const blue = num & 255;
20 |
21 | if (opacity) {
22 | return `rgba(${red}, ${green}, ${blue}, ${opacity})`;
23 | }
24 | return `rgb(${red}, ${green}, ${blue})`;
25 | };
26 |
27 | const Pins = (props) => {
28 | const { pins } = props;
29 |
30 | const markerStyle = {
31 | color: '#fc4b51',
32 | fillColor: "#fc4b51",
33 | opacity: 1,
34 | radius: 5,
35 | };
36 |
37 | const createClusterCustomIcon = (cluster) => {
38 | const count = cluster.getChildCount();
39 | let size = 'LargeXL';
40 |
41 | if (count < 10) {
42 | size = 'Small';
43 | }
44 | else if (count >= 10 && count < 100) {
45 | size = 'Medium';
46 | }
47 | else if (count >= 100 && count < 500) {
48 | size = 'Large';
49 | }
50 | const options = {
51 | cluster: `markerCluster${size}`,
52 | circle1: `markerCluster${size}DivOne`,
53 | circle2: `markerCluster${size}DivTwo`,
54 | circle3: `markerCluster${size}DivThree`,
55 | circle4: `markerCluster${size}DivFour`,
56 | label: `markerCluster${size}Label`,
57 | };
58 |
59 | const clusterColor = hexToRgb('#fc4b51');
60 | const circleStyle1 = `background-color: ${clusterColor.slice(0, -1)}, 0.05)`;
61 | const circleStyle2 = `background-color: ${clusterColor.slice(0, -1)}, 0.15)`;
62 | const circleStyle3 = `background-color: ${clusterColor.slice(0, -1)}, 0.25)`;
63 | const circleStyle4 = `background-color: ${clusterColor.slice(0, -1)}, 0.65)`;
64 |
65 |
66 | return L.divIcon({
67 | html:
68 | `
69 |
70 |
71 |
72 | ${count}
73 |
74 |
75 |
76 |
`,
77 | className: `${options.cluster}`,
78 | });
79 | };
80 |
81 | const Markers = pins
82 | .map((p, i) => (
83 |
88 | ));
89 |
90 | return (
91 |
101 | {Markers}
102 |
103 | );
104 | };
105 |
106 | export default Pins;
107 |
--------------------------------------------------------------------------------
/src/map-widget.css:
--------------------------------------------------------------------------------
1 | .mapContainer {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | .mapContainer .item {
8 | display: flex;
9 | align-items: center;
10 | justify-content: center;
11 | box-shadow: 31px -32px 70px 10px rgba(0, 0, 0, 0.55);
12 | position: relative;
13 | overflow: hidden;
14 | height: calc(100% - 16px) !important;
15 | }
16 |
17 | .mapContainer .textItemContainer {
18 | position: relative !important;
19 | }
20 |
21 | .mapContainer .textItemWrapper {
22 | overflow: hidden;
23 | display: flex;
24 | align-items: center;
25 | justify-content: center;
26 | }
27 |
28 | .flyoverContainer::before {
29 | content: ' ';
30 | position: relative;
31 | display: inline-block;
32 | width: 0;
33 | height: 0;
34 | border-style: solid;
35 | border-width: 100px 50px 0 0;
36 | border-color: transparent #fff transparent transparent;
37 | transform: rotate(210deg);
38 | opacity: 0.7;
39 | top: 385px;
40 | left: -20px;
41 | }
42 |
43 | .markerClusterSmall {
44 | border-radius: 20px;
45 | font-size: 0.6vw;
46 | color: #fff;
47 | font-weight: 900;
48 | font-family: Open Sans;
49 | text-transform: uppercase;
50 | }
51 |
52 | .markerClusterSmallDivOne {
53 | position: absolute;
54 | margin-left: -5px;
55 | margin-top: -5px;
56 | border-radius: 7.5em;
57 | display: flex;
58 | justify-content: center;
59 | align-items: center;
60 | animation: fadein 3s;
61 | }
62 |
63 | .markerClusterSmallDivTwo {
64 | position: absolute;
65 | width: 5em;
66 | height: 5em;
67 | border-radius: 6em;
68 | display: flex;
69 | justify-content: center;
70 | align-items: center;
71 | animation: fadein 2.5s;
72 | }
73 |
74 | .markerClusterSmallDivThree {
75 | width: 3.5em;
76 | height: 3.5em;
77 | border-radius: 4.5em;
78 | display: flex;
79 | justify-content: center;
80 | align-items: center;
81 | animation: fadein 2s;
82 | }
83 |
84 | .markerClusterSmallDivFour {
85 | width: 2em;
86 | height: 2em;
87 | border-radius: 3em;
88 | display: flex;
89 | justify-content: center;
90 | align-items: center;
91 | animation: fadein 1.5s;
92 | }
93 |
94 | .markerClusterSmallLabel {
95 | display: flex;
96 | justify-content: center;
97 | align-items: center;
98 | }
99 |
100 | .markerClusterMedium {
101 | border-radius: 40px;
102 | font-size: 0.6vw;
103 | color: #fff;
104 | font-weight: 900;
105 | font-family: Open Sans;
106 | text-transform: uppercase;
107 | }
108 |
109 | .markerClusterMediumDivOne {
110 | position: absolute;
111 | margin-left: -5px;
112 | margin-top: -5px;
113 | border-radius: 9.5em;
114 | display: flex;
115 | justify-content: center;
116 | align-items: center;
117 | animation: fadein 3s;
118 | }
119 |
120 | .markerClusterMediumDivTwo {
121 | position: absolute;
122 | width: 6.5em;
123 | height: 6.5em;
124 | border-radius: 8em;
125 | display: flex;
126 | justify-content: center;
127 | align-items: center;
128 | animation: fadein 2.5s;
129 | }
130 |
131 | .markerClusterMediumDivThree {
132 | width: 5em;
133 | height: 5em;
134 | border-radius: 6.5em;
135 | display: flex;
136 | justify-content: center;
137 | align-items: center;
138 | animation: fadein 2s;
139 | }
140 |
141 | .markerClusterMediumDivFour {
142 | width: 3.5em;
143 | height: 3.5em;
144 | border-radius: 5em;
145 | display: flex;
146 | justify-content: center;
147 | align-items: center;
148 | animation: fadein 1.5s;
149 | }
150 |
151 | .markerClusterMediumLabel {
152 | display: flex;
153 | justify-content: center;
154 | align-items: center;
155 | }
156 |
157 | .markerClusterLarge {
158 | border-radius: 70px;
159 | font-size: 0.6vw;
160 | color: #fff;
161 | font-weight: 900;
162 | font-family: Open Sans;
163 | text-transform: uppercase;
164 | }
165 |
166 | .markerClusterLargeDivOne {
167 | position: absolute;
168 | margin-left: -5px;
169 | margin-top: -5px;
170 | border-radius: 12em;
171 | display: flex;
172 | justify-content: center;
173 | align-items: center;
174 | animation: fadein 3s;
175 | }
176 |
177 | .markerClusterLargeDivTwo {
178 | position: absolute;
179 | width: 9em;
180 | height: 9em;
181 | border-radius: 10.5em;
182 | display: flex;
183 | justify-content: center;
184 | align-items: center;
185 | animation: fadein 2.5s;
186 | }
187 |
188 | .markerClusterLargeDivThree {
189 | width: 7.5em;
190 | height: 7.5em;
191 | border-radius: 9em;
192 | display: flex;
193 | justify-content: center;
194 | align-items: center;
195 | animation: fadein 2s;
196 | }
197 |
198 | .markerClusterLargeDivFour {
199 | padding: 1px;
200 | width: 6em;
201 | height: 6em;
202 | border-radius: 7.5em;
203 | display: flex;
204 | justify-content: center;
205 | align-items: center;
206 | animation: fadein 1.5s;
207 | }
208 |
209 | .markerClusterLargeLabel {
210 | display: flex;
211 | justify-content: center;
212 | align-items: center;
213 | }
214 |
215 | .markerClusterLargeXL {
216 | border-radius: 70px;
217 | font-size: 0.6vw;
218 | color: #fff;
219 | font-weight: 900;
220 | font-family: Open Sans;
221 | text-transform: uppercase;
222 | }
223 |
224 | .markerClusterLargeXLDivOne {
225 | position: absolute;
226 | margin-left: -5px;
227 | margin-top: -5px;
228 | border-radius: 12em;
229 | display: flex;
230 | justify-content: center;
231 | align-items: center;
232 | animation: fadein 3s;
233 | }
234 |
235 | .markerClusterLargeXLDivTwo {
236 | position: absolute;
237 | width: 10.5em;
238 | height: 10.5em;
239 | border-radius: 10.5em;
240 | display: flex;
241 | justify-content: center;
242 | align-items: center;
243 | animation: fadein 2.5s;
244 | }
245 |
246 | .markerClusterLargeXLDivThree {
247 | width: 9em;
248 | height: 9em;
249 | border-radius: 9em;
250 | display: flex;
251 | justify-content: center;
252 | align-items: center;
253 | animation: fadein 2s;
254 | }
255 |
256 | .markerClusterLargeXLDivFour {
257 | padding: 1px;
258 | width: 7.5em;
259 | height: 7.5em;
260 | border-radius: 7.5em;
261 | display: flex;
262 | justify-content: center;
263 | align-items: center;
264 | animation: fadein 1.5s;
265 | }
266 |
267 | .markerClusterLargeXLLabel {
268 | display: flex;
269 | justify-content: center;
270 | align-items: center;
271 | }
272 |
273 | @keyframes fadein {
274 | from {
275 | opacity: 0;
276 | }
277 | to {
278 | opacity: 1;
279 | }
280 | }
281 |
282 | .circle {
283 | animation: fadein 1.5s;
284 | }
285 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebPackPlugin = require("html-webpack-plugin");
3 |
4 | module.exports = {
5 | devtool: 'source-map',
6 | entry: [
7 | './index.js',
8 | ],
9 | output: {
10 | filename: 'bundle.js',
11 | path: path.resolve(__dirname, 'dist')
12 | },
13 | devServer: {
14 | contentBase: "./dist"
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.css$/,
20 | use: [ 'style-loader', 'css-loader' ]
21 | },
22 | {
23 | test: /\.html$/,
24 | use: [
25 | {
26 | loader: "html-loader"
27 | }
28 | ]
29 | },
30 | {
31 | test: /\.js$/,
32 | exclude: /node_modules/,
33 | use: [
34 | {
35 | loader: 'babel-loader',
36 | query: {
37 | retainLines: true
38 | }
39 | }
40 | ]
41 | },
42 |
43 | ]
44 | },
45 | plugins: [
46 | new HtmlWebPackPlugin({
47 | template: "./index.html",
48 | filename: "./index.html"
49 | })
50 | ]
51 | };
52 |
--------------------------------------------------------------------------------