Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance with many markers #224

Merged
merged 11 commits into from
May 30, 2016
Merged

Performance with many markers #224

merged 11 commits into from
May 30, 2016

Conversation

rewop
Copy link
Collaborator

@rewop rewop commented Mar 8, 2016

This PR solves the performance problems on the map, when rendering many markers.

Problem

The problem is that every component in the module needs a reference to the mapHolder instance to perform some operation on the google map.

GoogleMapHolder passes itself down to the children using props, but to do so it always needs to clone all the children that is rendering.

So if you render many markers, GoogleMapHolder clones all the markers every time its render function fires.

Solution

To solve the issue, I place the mapHolder instance in a context instead. This way all the components down the tree can access the mapHolder instance just by requesting it from the context (code for the marker).

This way the cloning of each child is not needed and the performance are (almost) the same if you used the plain google map library.

An example is the following:

import React, { Component } from "react";
import { GoogleMapLoader, GoogleMap, Marker, OverlayView } from "../../../../../../../../Development/react-google-maps";
// import { simplified } from "../../knd-elements/GoogleMapsThemes";

const STYLES = {
    mapContainer: {
        height: "400px"
    },
    overlayView: {
        background: "white",
        border: "1px solid #ccc",
        padding: 15
    }
};

function getRandomInRange(from, to, fixed) {
    // .toFixed() returns string, so ' * 1' is a trick to convert to number
    return (Math.random() * (to - from) + from).toFixed(fixed) * 1;
}

function createLatLng() {
    return {
        lat: getRandomInRange(-90, 90),
        lng: getRandomInRange(-180, 180)
    };
}

function createMarker(index) {
    return {
        position: createLatLng(),
        index
    };
}

function getMarkers() {
    let markers = [];
    for (let i = 0; i < 3000; i++) {
        markers = [
            ...markers,
            createMarker(i)
        ];
    }
    return markers;
}

class MapTest extends Component {

    constructor() {
        super();
        this.state = {
            markers: [],
            loaded: false
        };
    }

    componentDidMount() {
        const markers = getMarkers();
        this.setState({
            markers,
            markerComponents: markers.map((marker, index) => (
                <Marker
                    {...marker}
                    onClick={() => this.onSelectMarker(marker)}
                    key={index}
                />
            ))
        });
    }

    onSelectMarker(marker) {
        this.setState({
            selectedMarker: marker
        });
    }

    onCloseOverlay() {
        this.setState({
            selectedMarker: null
        });
    }

    render() {

        return (
            <GoogleMapLoader
                containerElement={
                    <div {... this.props} style={STYLES.mapContainer} />
                }
                googleMapElement={
                    <GoogleMap
                        defaultZoom={7}
                        defaultCenter={{
                            lat: 52.3702157,
                            lng: 4.8951679
                        }}
                    >
                        {this.state.selectedMarker &&
                            <OverlayView
                                position={this.state.selectedMarker.position}
                                mapPaneName={OverlayView.OVERLAY_MOUSE_TARGET}
                            >
                                <div style={STYLES.overlayView}>
                                    <h1>OverlayView</h1>
                                    <p>Marker: {this.state.selectedMarker.index}</p>
                                    <button onClick={() => this.onCloseOverlay()}>
                                      Close me
                                    </button>
                                </div>
                            </OverlayView>
                        }
                        {this.state.markerComponents}
                    </GoogleMap>
                }
            />
        );
    }
}

Each click on the marker on this map, takes more than 3 seconds to update it. This pr aims at solving this problem, and dramatically improve the performance issues.

Resolve #135

Simone Potenza added 2 commits March 8, 2016 18:27
Now GoogleMapHolder just renders children, and defines a context to
provide the reference to itself. In this way, to request the original
google map obejct, it is only necessary to request it from the context.

This improves rendering of 3000 markers by 3 seconds.
…his incresases the performance of the map, when rendering many markers
@cristiandley
Copy link
Collaborator

Why is this not merged yet ?

@cristiandley cristiandley mentioned this pull request May 13, 2016
@vinceprofeta
Copy link

+1

@vinceprofeta
Copy link

vinceprofeta commented May 16, 2016

@rewop after taking a deeper look into your solution, I noticed that if you wanted to update the marker in any way (size, color, icon, etc) you cant. I would suggest maybe being able to pass in your own function to CDU and that way you can control all updates based on which features you may want. You could also do a comparison of each marker, but that may also have performance issues.

@rewop
Copy link
Collaborator Author

rewop commented May 16, 2016

@vinceprofeta I worked on this PR quite long ago, and I need a moment to catch up with the changes. I see your point, and it is indeed a good one. Do you have an example that I can use to reproduce the use case? I will work on this asap.

@vinceprofeta
Copy link

 const markers = _.map(locations, (location, i) => {
    const icon = this.determineIcon(location, i);
    return (
      <Marker
      key={location._id+'-marker'}
      defaultAnimation={2}
      zIndex={icon.zIndex}
      icon={{
        url: icon.icon,
        size: new google.maps.Size(icon.size,icon.size),
        scaledSize: new google.maps.Size(icon.size,icon.size)
      }}
      position={{lng: location.Location[0], lat: location.Location[1]}}
      {...location}
      onClick={this.onMarkerClick.bind(this, location)}
      onMouseEnter={this.hover.bind(this, location, true)}
      onMouseLeave={this.hover.bind(this, false)} />
    );
  })

determineIcon(location, i) {
  const activeId = this.props.activeLocationId === location._id
  if (activeId) {
    return {
      icon: location.OnOffPremise === 'On Premise' ? 'src/images/barrellActive@2x.png' : 'src/images/barrellCartActive@2x.png',
      size: 110,
      zIndex: 990
    }
  } else {
    return {
      icon: location.OnOffPremise === 'On Premise' ? 'src/images/barrell@2x.png' : 'src/images/barrellCart@2x.png',
      size: 67,
      zIndex: i
    }
  }
}

@vinceprofeta
Copy link

@rewop within render I have a loop to create the markers and within the loop I have a function to determine whether that icon should be the active icon (bigger and a different color). For your example you could just adjust the size of the marker.

@vinceprofeta
Copy link

@tomchentw Do you have an opinion on this? Making this update significantly helped performance, and would love to get a fix in production.

Vincent.Profeta and others added 2 commits May 16, 2016 23:41
@cristiandley
Copy link
Collaborator

@vinceprofeta @rewop i needed some features to quickly put them into development/production so i created a repo based on this one. https://www.npmjs.com/package/react-gl-maps if you want to pull this i will be glad.

@rewop
Copy link
Collaborator Author

rewop commented May 24, 2016

@cristiandley thanks. I have had a really busy week at work, and I couldn't put myself into it yet. I hope by the end of this week to manage.

@rewop
Copy link
Collaborator Author

rewop commented May 25, 2016

NOTE: I updated the description to better explain my solution here.

@tomchentw
Copy link
Owner

@rewop Thanks for all the hard work for this. Much appreciated.

Would you please revert the changes made to lib files so that this PR only reflects changes in src? That would reduce the noise around the code.

Also, why adding shouldComponentUpdate to Marker component?

@@ -88,6 +99,10 @@ export default class Marker extends Component {
}
}

shouldComponentUpdate() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why adding shouldComponentUpdate to Marker component?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first this was my approach to try fix performance, that is why I implemented it. However, later I didn't remove it because it because I thought that the marker doesn't need to be updated, unless it is completely replaced.

However for the sake of performance, this shouldComponentUpdate is not needed.

@tomchentw what do you think? I can push a change for it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this for this PR. I want to implement sCU for components in another pull requests, thus making changes as small as possible.

@rewop
Copy link
Collaborator Author

rewop commented May 29, 2016

@tomchentw I reverted the lib folder. I am waiting for your answer about shouldComponentUpdate in Marker component.

@rewop
Copy link
Collaborator Author

rewop commented May 29, 2016

@tomchentw all changes done

@@ -14,6 +15,10 @@ import {
markerEventPropTypes,
} from "./creators/MarkerCreator";

import GoogleMapHolder from "./creators/GoogleMapHolder";

import _ from 'lodash';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove lodash for now.

@@ -84,6 +84,7 @@
"can-use-dom": "^0.1.0",
"google-maps-infobox": "^1.1.13",
"invariant": "^2.1.1",
"lodash": "^4.12.0",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove lodash for now.

@tomchentw
Copy link
Owner

@rewop thanks a LOT! Just one more thing to do then we're all set!

@rewop
Copy link
Collaborator Author

rewop commented May 29, 2016

@tomchentw done the changes. I also linted, to make sure I didn't miss anything. Let me know if more is needed.

@@ -84,8 +84,6 @@
"can-use-dom": "^0.1.0",
"google-maps-infobox": "^1.1.13",
"invariant": "^2.1.1",
"lodash": "^4.12.0",
"lodash.isequal": "^3.0.4",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no. You have to keep lodash.isequal

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. I knew I had to keep it. Strange it was removed because I double checked. Must have made a mistake. Going to fix it now.

@tomchentw
Copy link
Owner

@rewop please bring back lodash.isequal package.

@tomchentw
Copy link
Owner

Looks great so far. I'll review this again tomorrow morning and merge it.

@tomchentw
Copy link
Owner

I've tested on my local and it's working great. Thanks again!

@tomchentw tomchentw merged commit c2d265c into tomchentw:master May 30, 2016
@tomchentw tomchentw mentioned this pull request May 30, 2016
8 tasks
@tomchentw tomchentw added this to the 5.0.0 milestone May 30, 2016
@tomchentw
Copy link
Owner

Released v5.0.0.

This version(5.0.0) will never be tagged latest on npm since there's a major rewrite ongoing for 6.0.0. See more details in #267 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Performance issues when changing a marker icon with lots of markers
4 participants