Repository: python-visualization/folium

Issue:
Layer selector disappears when using FastMarkerCluster()
When I trying to add a FastMarkerCluster() layer to my multilayer map, the layer selector in the top right of the browser window disappears.

#### Code Sample:

```python
import pandas as pd
import folium

df = pd.read_csv(r'raw_data.csv')
data = df[['lat', 'long']].values.tolist()

m = folium.Map() 
folium.plugins.HeatMap(data, name="layer1").add_to(m)
folium.plugins.HeatMap(data, name="layer2").add_to(m)
#folium.plugins.FastMarkerCluster(data, name="layer3").add_to(m)

folium.LayerControl().add_to(m)
m.save("map.html")

```
#### Problem description

When I run the above as is, I get a layer selector in the top right which I can use to switch on/off the two heatmap layers. When I uncomment the FastMarkerCluster() line and run the code, the cluster layer is added to the map, however the layer selector is no longer present.

#### Expected Output

I expect the layer selector in the top right corner of the browser window to be present when adding layers using FastMarkerCluster()

#### Output of ``folium.__version__``
'0.5.0+111.gc228c26'

Discussion:

Patch:
diff --git a/CHANGES.txt b/CHANGES.txt
index fee3bfb2..f845af64 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,8 @@
 0.6.0
 ~~~~~
 
+- `Popup` accepts new arguments `show` (render open on page load) and `sticky` (popups
+  only close when explicitly clicked) (jwhendy #778)
 - Added leaflet-search plugin (ghandic #759)
 - Improved Vector Layers docs, notebooks, and optional arguments (ocefpaf #731)
 - Implemented `export=False/True` option to the Draw plugin layer for saving
@@ -24,6 +26,7 @@ Bug Fixes
 - Fixed numpy array bug (#749) in _flatten
 - Unify `get_bounds` routine to avoid wrong responses
 - If Path option `fill_color` is present it will override `fill=False`
+- Fix disappearing layer control when using FastMarkerCluster (conengmo #866)
 
 0.5.0
 ~~~~~
diff --git a/folium/__init__.py b/folium/__init__.py
index 2574ff8e..e03ff878 100644
--- a/folium/__init__.py
+++ b/folium/__init__.py
@@ -23,6 +23,11 @@ from folium.map import (
 
 from folium.vector_layers import Circle, CircleMarker, PolyLine, Polygon, Rectangle  # noqa
 
+import branca
+if tuple(int(x) for x in branca.__version__.split('.')) < (0, 3, 0):
+    raise ImportError('branca version 0.3.0 or higher is required. '
+                      'Update branca with e.g. `pip install branca --upgrade`.')
+
 __version__ = get_versions()['version']
 del get_versions
 
diff --git a/folium/map.py b/folium/map.py
index 4680510c..c2fb7261 100644
--- a/folium/map.py
+++ b/folium/map.py
@@ -276,23 +276,30 @@ class Popup(Element):
         True if the popup is a template that needs to the rendered first.
     max_width: int, default 300
         The maximal width of the popup.
+    show: bool, default False
+        True renders the popup open on page load.
+    sticky: bool, default False
+        True prevents map and other popup clicks from closing.
     """
     _template = Template(u"""
-            var {{this.get_name()}} = L.popup({maxWidth: '{{this.max_width}}'});
+            var {{this.get_name()}} = L.popup({maxWidth: '{{this.max_width}}'
+            {% if this.show or this.sticky %}, autoClose: false{% endif %}
+            {% if this.sticky %}, closeOnClick: false{% endif %}});
 
             {% for name, element in this.html._children.items() %}
                 var {{name}} = $('{{element.render(**kwargs).replace('\\n',' ')}}')[0];
                 {{this.get_name()}}.setContent({{name}});
             {% endfor %}
 
-            {{this._parent.get_name()}}.bindPopup({{this.get_name()}});
+            {{this._parent.get_name()}}.bindPopup({{this.get_name()}})
+            {% if this.show %}.openPopup(){% endif %};
 
             {% for name, element in this.script._children.items() %}
                 {{element.render()}}
             {% endfor %}
         """)  # noqa
 
-    def __init__(self, html=None, parse_html=False, max_width=300):
+    def __init__(self, html=None, parse_html=False, max_width=300, show=False, sticky=False):
         super(Popup, self).__init__()
         self._name = 'Popup'
         self.header = Element()
@@ -311,6 +318,8 @@ class Popup(Element):
             self.html.add_child(Html(text_type(html), script=script))
 
         self.max_width = max_width
+        self.show = show
+        self.sticky = sticky
 
     def render(self, **kwargs):
         """Renders the HTML representation of the element."""
diff --git a/folium/plugins/fast_marker_cluster.py b/folium/plugins/fast_marker_cluster.py
index b42cd281..d4a8f756 100644
--- a/folium/plugins/fast_marker_cluster.py
+++ b/folium/plugins/fast_marker_cluster.py
@@ -41,11 +41,11 @@ class FastMarkerCluster(MarkerCluster):
     """
     _template = Template(u"""
             {% macro script(this, kwargs) %}
-            {{this._callback}}
 
-            (function(){
-                var data = {{this._data}};
-                var map = {{this._parent.get_name()}};
+            var {{ this.get_name() }} = (function(){
+                {{this._callback}}
+
+                var data = {{ this._data }};
                 var cluster = L.markerClusterGroup();
 
                 for (var i = 0; i < data.length; i++) {
@@ -54,7 +54,8 @@ class FastMarkerCluster(MarkerCluster):
                     marker.addTo(cluster);
                 }
 
-                cluster.addTo(map);
+                cluster.addTo({{ this._parent.get_name() }});
+                return cluster;
             })();
             {% endmacro %}""")
 
@@ -66,15 +67,12 @@ class FastMarkerCluster(MarkerCluster):
         self._data = _validate_coordinates(data)
 
         if callback is None:
-            self._callback = ('var callback;\n' +
-                              'callback = function (row) {\n' +
-                              '\tvar icon, marker;\n' +
-                              '\t// Returns a L.marker object\n' +
-                              '\ticon = L.AwesomeMarkers.icon();\n' +
-                              '\tmarker = L.marker(new L.LatLng(row[0], ' +
-                              'row[1]));\n' +
-                              '\tmarker.setIcon(icon);\n' +
-                              '\treturn marker;\n' +
-                              '};')
+            self._callback = """
+                var callback = function (row) {
+                    var icon = L.AwesomeMarkers.icon();
+                    var marker = L.marker(new L.LatLng(row[0], row[1]));
+                    marker.setIcon(icon);
+                    return marker;
+                };"""
         else:
             self._callback = 'var callback = {};'.format(callback)
diff --git a/requirements.txt b/requirements.txt
index f2c4eb37..69e1a836 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-branca
+branca>=0.3.0
 jinja2
 numpy
 requests


Test patch:
diff --git a/tests/test_map.py b/tests/test_map.py
index 7a2d49ae..846a2a36 100644
--- a/tests/test_map.py
+++ b/tests/test_map.py
@@ -8,6 +8,7 @@ Folium map Tests
 
 from __future__ import (absolute_import, division, print_function)
 
+from folium import Map
 from folium.map import Popup
 
 
@@ -18,6 +19,10 @@ tmpl = u"""
 """.format
 
 
+def _normalize(rendered):
+    return ''.join(rendered.split())
+
+
 def test_popup_ascii():
     popup = Popup('Some text.')
     _id = list(popup.html._children.keys())[0]
@@ -52,3 +57,33 @@ def test_popup_unicode():
         'text': u'Ça c&#39;est chouette',
     }
     assert ''.join(popup.html.render().split()) == ''.join(tmpl(**kw).split())
+
+
+def test_popup_sticky():
+    m = Map()
+    popup = Popup('Some text.', sticky=True).add_to(m)
+    rendered = popup._template.render(this=popup, kwargs={})
+    expected = """
+    var {popup_name} = L.popup({{maxWidth: \'300\', autoClose: false, closeOnClick: false}});
+    var {html_name} = $(\'<div id="{html_name}" style="width: 100.0%; height: 100.0%;">Some text.</div>\')[0];
+    {popup_name}.setContent({html_name});
+    {map_name}.bindPopup({popup_name});
+    """.format(popup_name=popup.get_name(),
+               html_name=list(popup.html._children.keys())[0],
+               map_name=m.get_name())
+    assert _normalize(rendered) == _normalize(expected)
+
+
+def test_popup_show():
+    m = Map()
+    popup = Popup('Some text.', show=True).add_to(m)
+    rendered = popup._template.render(this=popup, kwargs={})
+    expected = """
+    var {popup_name} = L.popup({{maxWidth: \'300\' , autoClose: false}});
+    var {html_name} = $(\'<div id="{html_name}" style="width: 100.0%; height: 100.0%;">Some text.</div>\')[0];
+    {popup_name}.setContent({html_name});
+    {map_name}.bindPopup({popup_name}).openPopup();
+    """.format(popup_name=popup.get_name(),
+               html_name=list(popup.html._children.keys())[0],
+               map_name=m.get_name())
+    assert _normalize(rendered) == _normalize(expected)