Repository: istarkov/google-map-clustering-example
Branch: master
Commit: c2c617eb3c33
Files: 16
Total size: 13.4 KB
Directory structure:
gitextract_wovvt_uj/
├── .eslintrc
├── .gitignore
├── README.md
├── build/
│ └── index.html
├── config/
│ └── loaders.js
├── package.json
└── src/
├── GMap.js
├── Layout.js
├── Layout.sass
├── Main.js
├── Main.sass
├── data/
│ └── fakeData.js
└── markers/
├── ClusterMarker.js
├── ClusterMarker.sass
├── SimpleMarker.js
└── SimpleMarker.sass
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc
================================================
{
"extends": "airbnb",
"parser": "babel-eslint",
"rules": {
"no-nested-ternary": 0,
"react/prop-types": 0
}
}
================================================
FILE: .gitignore
================================================
/build/*
!/build/index.html
/node_modules
npm-debug.log
================================================
FILE: README.md
================================================
# google-map-clustering-example
Clustering example for [google-map-react](https://github.com/istarkov/google-map-react)
[Click here to view](http://istarkov.github.io/google-map-clustering-example/)
### [Kotatsu](https://github.com/Yomguithereal/kotatsu)
I was heavily inspired of [kotatsu](https://github.com/Yomguithereal/kotatsu) project.
This project uses `kotatsu` as run engine.
To run this project locally (with hot reload and other fine kotatsu things)
```bash
npm install
npm run start
# open your browser at localhost:4000
```
To build
```bash
npm install
npm run build
```
### [MapBox](https://github.com/mapbox) and [Mourner](https://github.com/mourner)
Any map-geo library you want, always can be found there.
This project also uses [MapBox library written by Mourner](https://github.com/mapbox/supercluster)
### [Recompose by @acdlite](https://github.com/acdlite/recompose)
My lovest library, [recompose](https://github.com/acdlite/recompose) is heavily used in this project.
================================================
FILE: build/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>Map clustering example</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./bundle.js"></script>
</body>
</html>
================================================
FILE: config/loaders.js
================================================
var path = require('path'); // eslint-disable-line no-var
var autoprefixer = require('autoprefixer'); // eslint-disable-line no-var
module.exports = {
postcss: [
autoprefixer({ browsers: ['last 2 versions'] }),
],
module: {
loaders: [
{
test: /\.sass$/,
loaders: [
'style-loader',
'css-loader?modules&importLoaders=2&localIdentName=[name]__[local]',
'postcss-loader',
`sass-loader?precision=10&indentedSyntax=sass`,
],
include: [
path.join(__dirname, '../src'),
],
},
{
test: /\.css$/,
loaders: [
'style-loader',
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]',
'postcss-loader',
],
include: [
path.join(__dirname, '../src'),
path.join(__dirname, '../node_modules'),
],
},
{
test: /\.svg$/,
loaders: ['url-loader?limit=7000'],
},
],
},
};
================================================
FILE: package.json
================================================
{
"name": "google-map-clustering-example",
"version": "0.1.0",
"description": "clustering example for google-map-react",
"main": "index.js",
"scripts": {
"start": "kotatsu serve --port 4000 --config ./config/loaders.js --presets es2015,stage-0,react,react-hmre ./src/Main.js",
"build": "NODE_ENV=production kotatsu build client --minify --config ./config/loaders.js --presets es2015,stage-0,react ./src/Main.js -o build",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Ivan Starkov",
"license": "MIT",
"devDependencies": {
"autoprefixer": "^6.3.1",
"babel-cli": "^6.4.5",
"babel-eslint": "^4.1.8",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-react-hmre": "^1.0.1",
"babel-preset-stage-0": "^6.3.13",
"css-loader": "^0.23.1",
"eslint": "^1.10.3",
"eslint-config-airbnb": "^4.0.0",
"eslint-plugin-react": "^3.16.1",
"file-loader": "^0.8.5",
"kotatsu": "^0.9.1",
"node-sass": "^3.4.2",
"normalize.css": "^3.0.3",
"postcss-loader": "^0.8.0",
"sass-loader": "^3.1.2",
"style-loader": "^0.13.0",
"url-loader": "^0.5.7"
},
"dependencies": {
"google-map-react": "^0.14.5",
"points-cluster": "^0.1.4",
"react": "^15.1.0",
"react-dom": "^15.1.0",
"react-motion": "^0.4.4",
"recompose": "^0.20.0"
}
}
================================================
FILE: src/GMap.js
================================================
import React from 'react';
import compose from 'recompose/compose';
import defaultProps from 'recompose/defaultProps';
import withState from 'recompose/withState';
import withHandlers from 'recompose/withHandlers';
import withPropsOnChange from 'recompose/withPropsOnChange';
import GoogleMapReact from 'google-map-react';
import ClusterMarker from './markers/ClusterMarker';
import SimpleMarker from './markers/SimpleMarker';
import supercluster from 'points-cluster';
import { susolvkaCoords, markersData } from './data/fakeData';
export const gMap = ({
style, hoverDistance, options,
mapProps: { center, zoom },
onChange, onChildMouseEnter, onChildMouseLeave,
clusters,
}) => (
<GoogleMapReact
style={style}
options={options}
hoverDistance={hoverDistance}
center={center}
zoom={zoom}
onChange={onChange}
onChildMouseEnter={onChildMouseEnter}
onChildMouseLeave={onChildMouseLeave}
>
{
clusters
.map(({ ...markerProps, id, numPoints }) => (
numPoints === 1
? <SimpleMarker key={id} {...markerProps} />
: <ClusterMarker key={id} {...markerProps} />
))
}
</GoogleMapReact>
);
export const gMapHOC = compose(
defaultProps({
clusterRadius: 60,
hoverDistance: 30,
options: {
minZoom: 3,
maxZoom: 15,
},
style: {
position: 'relative',
margin: 0,
padding: 0,
flex: 1,
},
}),
// withState so you could change markers if you want
withState(
'markers',
'setMarkers',
markersData
),
withState(
'hoveredMarkerId',
'setHoveredMarkerId',
-1
),
withState(
'mapProps',
'setMapProps',
{
center: susolvkaCoords,
zoom: 10,
}
),
// describe events
withHandlers({
onChange: ({ setMapProps }) => ({ center, zoom, bounds }) => {
setMapProps({ center, zoom, bounds });
},
onChildMouseEnter: ({ setHoveredMarkerId }) => (hoverKey, { id }) => {
setHoveredMarkerId(id);
},
onChildMouseLeave: ({ setHoveredMarkerId }) => (/* hoverKey, childProps */) => {
setHoveredMarkerId(-1);
},
}),
// precalculate clusters if markers data has changed
withPropsOnChange(
['markers'],
({ markers = [], clusterRadius, options: { minZoom, maxZoom } }) => ({
getCluster: supercluster(
markers,
{
minZoom, // min zoom to generate clusters on
maxZoom, // max zoom level to cluster the points on
radius: clusterRadius, // cluster radius in pixels
}
),
})
),
// get clusters specific for current bounds and zoom
withPropsOnChange(
['mapProps', 'getCluster'],
({ mapProps, getCluster }) => ({
clusters: mapProps.bounds
? getCluster(mapProps)
.map(({ wx, wy, numPoints, points }) => ({
lat: wy,
lng: wx,
text: numPoints,
numPoints,
id: `${numPoints}_${points[0].id}`,
}))
: [],
})
),
// set hovered
withPropsOnChange(
['clusters', 'hoveredMarkerId'],
({ clusters, hoveredMarkerId }) => ({
clusters: clusters
.map(({ ...cluster, id }) => ({
...cluster,
hovered: id === hoveredMarkerId,
})),
})
),
);
export default gMapHOC(gMap);
================================================
FILE: src/Layout.js
================================================
import React, { Component } from 'react';
import compose from 'recompose/compose';
import defaultProps from 'recompose/defaultProps';
import layoutStyles from './Layout.sass';
import GMap from './GMap';
// for hmr to work I need the first class to extend Component
export class Layout extends Component {
render() {
const { styles: { layout, header, main, footer, logo } } = this.props;
return (
<div className={layout}>
<header className={header}>
<div>
Clustering example google-map-react (zoom, move to play with)
</div>
<div>
<a href="https://github.com/istarkov/google-map-clustering-example">
Star at github.com
</a>
</div>
</header>
<main className={main}>
<GMap />
</main>
<footer className={footer}>
<div>
<a href="https://github.com/istarkov">
Ivan Starkov
</a>
</div>
<div className={logo}></div>
<div>
<a href="https://twitter.com/icelabaratory">
@icelabaratory
</a>
</div>
</footer>
</div>
);
}
}
export const layoutHOC = compose(
defaultProps({
styles: layoutStyles,
})
);
export default layoutHOC(Layout);
================================================
FILE: src/Layout.sass
================================================
.layout
display: flex
min-height: 100vh
flex-direction: column
margin: 0 1px 0 1px
.header
height: 2em
background-color: #004336
color: #fff
display: flex
align-items: center
justify-content: space-between
padding: 0 10px 0 10px
a
color: #fff
.logo
width: 1.3em
height: 1.3em
margin: 0.3em
background-size: contain
background-repeat: no-repeat
background-image: url('https://avatars2.githubusercontent.com/u/5077042?v=3&s=40')
.main
flex: 1
display: flex
.footer
height: 2em
background-color: #004336
color: #fff
display: flex
align-items: center
justify-content: center
a
color: #fff
================================================
FILE: src/Main.js
================================================
// file: main.jsx
import React from 'react';
import { render } from 'react-dom';
import Layout from './Layout.js';
import 'normalize.css/normalize.css';
import './Main.sass';
const mountNode = document.getElementById('app');
render(<Layout />, mountNode);
================================================
FILE: src/Main.sass
================================================
:global(#app)
height: 100%
html, body
height: 100%
font-size: 14px
================================================
FILE: src/data/fakeData.js
================================================
const TOTAL_COUNT = 200;
export const susolvkaCoords = { lat: 60.814305, lng: 47.051773 };
export const markersData = [...Array(TOTAL_COUNT)].fill(0) // fill(0) for loose mode
.map((__, index) => ({
id: index,
lat: susolvkaCoords.lat +
0.01 * index *
Math.sin(30 * Math.PI * index / 180) *
Math.cos(50 * Math.PI * index / 180) + Math.sin(5 * index / 180),
lng: susolvkaCoords.lng +
0.01 * index *
Math.cos(70 + 23 * Math.PI * index / 180) *
Math.cos(50 * Math.PI * index / 180) + Math.sin(5 * index / 180),
}));
================================================
FILE: src/markers/ClusterMarker.js
================================================
import React from 'react';
import compose from 'recompose/compose';
import defaultProps from 'recompose/defaultProps';
import withPropsOnChange from 'recompose/withPropsOnChange';
import pure from 'recompose/pure';
import { Motion, spring } from 'react-motion';
import clusterMarkerStyles from './ClusterMarker.sass';
export const clusterMarker = ({
styles, text,
defaultMotionStyle, motionStyle,
}) => (
<Motion
defaultStyle={defaultMotionStyle}
style={motionStyle}
>
{
({ scale }) => (
<div
className={styles.marker}
style={{
transform: `translate3D(0,0,0) scale(${scale}, ${scale})`,
}}
>
<div
className={styles.text}
>
{text}
</div>
</div>
)
}
</Motion>
);
export const clusterMarkerHOC = compose(
defaultProps({
text: '0',
styles: clusterMarkerStyles,
initialScale: 0.6,
defaultScale: 1,
hoveredScale: 1.15,
hovered: false,
stiffness: 320,
damping: 7,
precision: 0.001,
}),
// pure optimization can cause some effects you don't want,
// don't use it in development for markers
pure,
withPropsOnChange(
['initialScale'],
({ initialScale, defaultScale, $prerender }) => ({
initialScale,
defaultMotionStyle: { scale: $prerender ? defaultScale : initialScale },
})
),
withPropsOnChange(
['hovered'],
({
hovered, hoveredScale, defaultScale,
stiffness, damping, precision,
}) => ({
hovered,
motionStyle: {
scale: spring(
hovered ? hoveredScale : defaultScale,
{ stiffness, damping, precision }
),
},
})
)
);
export default clusterMarkerHOC(clusterMarker);
================================================
FILE: src/markers/ClusterMarker.sass
================================================
@function stripUnits($number)
@return $number / ($number * 0 + 1)
$marker-width: 40px !default
$marker-height: 40px !default
$marker-border-width: 5px !default
$marker-font-size: 14px !default
.marker
position: absolute
cursor: pointer
width: $marker-width
height: $marker-height
left: -$marker-width / 2
top: -$marker-height / 2
border: $marker-border-width solid #004336
border-radius: 50%
background-color: white
text-align: center
color: #333
font-size: $marker-font-size
font-weight: bold
display: flex
align-items: center
justify-content: center
.text
:export
markerSize: stripUnits($marker-width)
================================================
FILE: src/markers/SimpleMarker.js
================================================
import React from 'react';
import compose from 'recompose/compose';
import defaultProps from 'recompose/defaultProps';
// import mapPropsOnChange from 'recompose/mapPropsOnChange';
import { Motion } from 'react-motion';
import { clusterMarkerHOC } from './ClusterMarker.js';
import simpleMarkerStyles from './SimpleMarker.sass';
export const simpleMarker = ({
styles,
defaultMotionStyle, motionStyle,
}) => (
<Motion
defaultStyle={defaultMotionStyle}
style={motionStyle}
>
{
({ scale }) => (
<div
className={styles.marker}
style={{
transform: `translate3D(0,0,0) scale(${scale}, ${scale})`,
}}
>
</div>
)
}
</Motion>
);
export const simpleMarkerHOC = compose(
defaultProps({
styles: simpleMarkerStyles,
initialScale: 0.3,
defaultScale: 0.6,
hoveredScale: 0.7,
}),
// resuse HOC
clusterMarkerHOC
);
export default simpleMarkerHOC(simpleMarker);
================================================
FILE: src/markers/SimpleMarker.sass
================================================
$markerWidth: 49px
$markerHeight: 64px
$originX: $markerWidth * .5
$originY: $markerHeight
.marker
background-image: url('./mapIcon.svg')
position: absolute
cursor: pointer
width: $markerWidth
height: $markerHeight
top: -$originY
left: -$originX
transform-origin: $originX $originY
margin: 0
padding: 0
gitextract_wovvt_uj/
├── .eslintrc
├── .gitignore
├── README.md
├── build/
│ └── index.html
├── config/
│ └── loaders.js
├── package.json
└── src/
├── GMap.js
├── Layout.js
├── Layout.sass
├── Main.js
├── Main.sass
├── data/
│ └── fakeData.js
└── markers/
├── ClusterMarker.js
├── ClusterMarker.sass
├── SimpleMarker.js
└── SimpleMarker.sass
SYMBOL INDEX (3 symbols across 2 files)
FILE: src/Layout.js
class Layout (line 7) | class Layout extends Component {
method render (line 8) | render() {
FILE: src/data/fakeData.js
constant TOTAL_COUNT (line 2) | const TOTAL_COUNT = 200;
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (15K chars).
[
{
"path": ".eslintrc",
"chars": 127,
"preview": "\n{\n \"extends\": \"airbnb\",\n \"parser\": \"babel-eslint\",\n \"rules\": {\n \"no-nested-ternary\": 0,\n \"react/prop-types\": 0"
},
{
"path": ".gitignore",
"chars": 56,
"preview": "/build/*\n!/build/index.html\n/node_modules\nnpm-debug.log\n"
},
{
"path": "README.md",
"chars": 1010,
"preview": "\n# google-map-clustering-example\n\nClustering example for [google-map-react](https://github.com/istarkov/google-map-react"
},
{
"path": "build/index.html",
"chars": 199,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>Map clustering example</title>\n </head>\n <body>\n <div id=\"app\"></div>\n "
},
{
"path": "config/loaders.js",
"chars": 1013,
"preview": "var path = require('path'); // eslint-disable-line no-var\nvar autoprefixer = require('autoprefixer'); // eslint-disable"
},
{
"path": "package.json",
"chars": 1384,
"preview": "{\n \"name\": \"google-map-clustering-example\",\n \"version\": \"0.1.0\",\n \"description\": \"clustering example for google-map-r"
},
{
"path": "src/GMap.js",
"chars": 3336,
"preview": "import React from 'react';\nimport compose from 'recompose/compose';\nimport defaultProps from 'recompose/defaultProps';\ni"
},
{
"path": "src/Layout.js",
"chars": 1333,
"preview": "import React, { Component } from 'react';\nimport compose from 'recompose/compose';\nimport defaultProps from 'recompose/d"
},
{
"path": "src/Layout.sass",
"chars": 651,
"preview": ".layout\n display: flex\n min-height: 100vh\n flex-direction: column\n margin: 0 1px 0 1px\n\n.header\n height: 2em\n back"
},
{
"path": "src/Main.js",
"chars": 259,
"preview": "// file: main.jsx\nimport React from 'react';\nimport { render } from 'react-dom';\nimport Layout from './Layout.js';\n\nimpo"
},
{
"path": "src/Main.sass",
"chars": 74,
"preview": ":global(#app)\n height: 100%\n\nhtml, body\n height: 100%\n font-size: 14px\n"
},
{
"path": "src/data/fakeData.js",
"chars": 567,
"preview": "\nconst TOTAL_COUNT = 200;\n\nexport const susolvkaCoords = { lat: 60.814305, lng: 47.051773 };\n\nexport const markersData ="
},
{
"path": "src/markers/ClusterMarker.js",
"chars": 1744,
"preview": "import React from 'react';\nimport compose from 'recompose/compose';\nimport defaultProps from 'recompose/defaultProps';\ni"
},
{
"path": "src/markers/ClusterMarker.sass",
"chars": 647,
"preview": "@function stripUnits($number)\n @return $number / ($number * 0 + 1)\n\n$marker-width: 40px !default\n$marker-height: 40px !"
},
{
"path": "src/markers/SimpleMarker.js",
"chars": 953,
"preview": "import React from 'react';\nimport compose from 'recompose/compose';\nimport defaultProps from 'recompose/defaultProps';\n/"
},
{
"path": "src/markers/SimpleMarker.sass",
"chars": 326,
"preview": "\n$markerWidth: 49px\n$markerHeight: 64px\n\n$originX: $markerWidth * .5\n$originY: $markerHeight\n\n.marker\n background-image"
}
]
About this extraction
This page contains the full source code of the istarkov/google-map-clustering-example GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (13.4 KB), approximately 4.2k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.