master c2c617eb3c33 cached
16 files
13.4 KB
4.2k tokens
3 symbols
1 requests
Download .txt
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
Download .txt
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
Download .txt
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.

Copied to clipboard!