Folium: Add custom html and JS to output html page

Created on 21 Feb 2015  Â·  19Comments  Â·  Source: python-visualization/folium

Is there a way to add custom HTML and JS to the output page? Something like following would be nice, for example

my_before = '<script> abc ... </script>'  
my_after = '<p> Arctic Ocean</p>'
create_map('abc.html', plugin_data=True, before=my_before, after=my_after)

Most helpful comment

Hi, it looks like m.create_map() does not exist anymore.

Is there another way of using a custom template?

All 19 comments

Hi @achourasia,

Sorry for the delay to answer. One way of doing this is with custom templates like this:

m.create_map(path='map.html', plugin_data_out=True, template='my_template.html')

To create your own template use the default template as a starting point.

thanks

@achourasia let me know if that works for you.

PS: You had my_after = '<p> Arctic Ocean</p>' in your example and, as an oceanographer, you got me curious about what you are building. If it is something you can share let me know. We might use your application as an example on how to use custom templates :grimacing:

I am toying with seismic data, but don't have all pieces working yet, will send you a note when have something usable. I will dig into template stuff once a core issue is resolved. BTW, if you have an example to pass user input back and forth using Flask that would be nice. My current setup is bit crummy where the map and geojson needs to load every time rather than just changing colors based on some selection.

That sounds like an interesting application. I do not have experience with Flask, but I am trying to learn to do something similar. I'll ping you if I get a working example.

@achourasia got lots of experience with flask, tell me what the plan is and im sure i can whip up an example

@birdage Great, I created a new issue for this https://github.com/python-visualization/folium/issues/88
Feel free to edit and modify the scenarios

@birdage Wait... my partner in crime is a flask expert and I did not know! We need to talk more :stuck_out_tongue_winking_eye:

Using:

m.create_map(path='map.html', plugin_data_out=True, template='my_template.html')
Gives this error:
AttributeError: 'Map' object has no attribute 'create_map'
The solutions in #227 do not work.
Looking to use custom template in creating map.

Hi, it looks like m.create_map() does not exist anymore.

Is there another way of using a custom template?

Hi, it looks like m.create_map() does not exist anymore.

Is there another way of using a custom template?

up

Maybe would it be interesting to add a add_custom_jsmethod to Map, that could allow to write direcly some parts directly in JavaScript in case the functionnality is not available in folium yet ?

For example, something in the class Maplike:

def add_custom_js(self, custom_js):
    macro = MacroElement()
    macro._template = (Template("""{% macro script(this, kwargs) %}"""
                                + custom_js
                                + """{% endmacro %}"""))
    self.get_root().add_child(macro)

It would then be possible to plug some callbacks in folium, for example:

import folium

m = folium.Map()

customjs = """
{{kwargs['map']}}.on('click', function(e) {
    alert(e.latlng);
});"""

m.add_custom_js(customjs)
m.save('customjs.html', map=m.get_name())

Please let me know if this approach seems interesting (and deserves a PR) or if there are other simpler ways to do that.

Hi @FabeG,

The example you show already seems quite simple to me :) I don't know if this functionality warrants its own method/class. Then again, it may be easier if users don't have to figure out how to use the macro script part.

The way folium is currently set up doesn't really work with class methods, but with combining classes in a tree.

How about having a simple Javascript class that implements your example? I'm thinking we could put it in features.py.

Hi @Conengmo,

Thank you for your feedback, but I'm afraid I wasn't clear enough: my goal wasn't to implement the previous (simple and not very useful) example, but this example was just here to illustrate what would be possible once implemented the possibility to add raw JavaScript code in the generated html file.
However, I followed your advice and added this simple JavaScript class in features.py:

class JavaScript(MacroElement):
    """
    Creates a small snippet of raw JavaScript.

    Parameters
    ----------
    script: string, representing the JavaScript code to insert in the html file
    html: string, representing html code to insert in the <body> section of the html file
    args: dict, mapping between python vars and folium vars

    """
    _template = Template(
        u"""{% macro html(this, args) %}
            {{this.html.render(**this.args)}}
            {% endmacro %}
            {% macro script(this, args) %}
            {{this.script.render(**this.args)}}
            {% endmacro %}"""
    )

    def __init__(self, script=None, html=None, args=None):
        super(JavaScript, self).__init__()
        self.script = Element(script)
        self.html = Element(html)
        self._name = "JavaScript"
        if args is None:
            args = {}
        self.args = args

With this new class, it is then possible to make a link between python vars and folium generated vars with the args argument. The previous example becomes:

import folium

m = folium.Map()
js = folium.features.JavaScript(script="""{{kwargs['map']}}.on('click', function(e) {  alert(e.latlng);});""",
                                            args={'map': m.get_name()})
m.add_child(js)
m.save('customjs.html')

but it will be also possible to implement more complicated (and useful) examples: the following example shows the possibility to make tooltip appear depending on the zoom level (taken from https://stackoverflow.com/questions/42364619/hide-tooltip-in-leaflet-for-a-zoom-range):

m = folium.Map(location=[45.372, -121.6972], tiles='Mapbox Bright')
js = folium.features.JavaScript(
    script="""{{kwargs['map']}}.on('zoomend', function() {
                  var lastZoom;
                  {{kwargs['map']}}.on('zoomend', function() {
                  var zoom = {{kwargs['map']}}.getZoom();
                  if (zoom < 7 && (!lastZoom || lastZoom >= 7)) {
                      {{kwargs['map']}}.eachLayer(function(l) {
                      if (l.getTooltip()) {
                          var tooltip = l.getTooltip();
                          l.unbindTooltip().bindTooltip(tooltip, {
                             permanent: false})
                      }});
                  } else if (zoom >= 7 && (!lastZoom || lastZoom < 7)) {
                     {{kwargs['map']}}.eachLayer(function(l) {
                     if (l.getTooltip()) {
                        var tooltip = l.getTooltip();
                        l.unbindTooltip().bindTooltip(tooltip, {
                           permanent: true})
                 }});}
                lastZoom = zoom;})})""",
    args={'map': m.get_name()})
m.add_child(js)

folium.Marker([45.3288, -121.6625], tooltip='Tooltip').add_to(m)
m.save('customjs.html')

I hope this clarified what I planned to do. Let me know if you think this JavaScript class can be useful.

This smells like a huge step forward. A lot of people here are riding the
PyJS fence. Thanks and kudos!

El lun., 4 mar. 2019 15:04, FabeG notifications@github.com escribió:

Hi @Conengmo https://github.com/Conengmo,

Thank you for your feedback, but I'm afraid I wasn't clear enough: my goal
wasn't to implement the previous (simple and not very useful) example, but
this example was just here to illustrate what would be possible once
implemented the possibility to add raw JavaScript code in the generated
html file.
However, I followed your advice and added this simple JavaScript class in
features.py:

class JavaScript(MacroElement):
"""
Creates a small snippet of raw JavaScript.

Parameters
----------
script: string, representing the JavaScript code to insert in the html file
html: string, representing html code to insert in the <body> section of the html file
args: dict, mapping between python vars and folium vars

"""
_template = Template(
    u"""{% macro html(this, args) %}
        {{this.html.render(**this.args)}}
        {% endmacro %}
        {% macro script(this, args) %}
        {{this.script.render(**this.args)}}
        {% endmacro %}"""
)

def __init__(self, script=None, html=None, args=None):
    super(JavaScript, self).__init__()
    self.script = Element(script)
    self.html = Element(html)
    self._name = "JavaScript"
    if args is None:
        args = {}
    self.args = args

With this new class, it is then possible to make a link between python
vars and folium generated vars with the args argument. The previous example
becomes:

import folium

m = folium.Map()
js = folium.features.JavaScript(script="""{{kwargs['map']}}.on('click', function(e) { alert(e.latlng);});""",
args={'map': m.get_name()})
m.add_child(js)
m.save('customjs.html')

but it will be also possible to implement more complicated (and useful)
examples: the following example shows the possibility to make tooltip
appear depending on the zoom level (taken from
https://stackoverflow.com/questions/42364619/hide-tooltip-in-leaflet-for-a-zoom-range
):

m = folium.Map(location=[45.372, -121.6972], tiles='Mapbox Bright')
js = folium.features.JavaScript(
script="""{{kwargs['map']}}.on('zoomend', function() {
var lastZoom;
{{kwargs['map']}}.on('zoomend', function() {
var zoom = {{kwargs['map']}}.getZoom();
if (zoom < 7 && (!lastZoom || lastZoom >= 7)) {
{{kwargs['map']}}.eachLayer(function(l) {
if (l.getTooltip()) {
var tooltip = l.getTooltip();
l.unbindTooltip().bindTooltip(tooltip, {
permanent: false})
}});
} else if (zoom >= 7 && (!lastZoom || lastZoom < 7)) {
{{kwargs['map']}}.eachLayer(function(l) {
if (l.getTooltip()) {
var tooltip = l.getTooltip();
l.unbindTooltip().bindTooltip(tooltip, {
permanent: true})
}});}
lastZoom = zoom;})})""",
args={'map': m.get_name()})
m.add_child(js)

folium.Marker([45.3288, -121.6625], tooltip='Tooltip').add_to(m)
m.save('customjs.html')

I hope this clarified what I planned to do. Let me know if you think this
JavaScript class can be useful.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/python-visualization/folium/issues/86#issuecomment-469353905,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGzduZVVNLdMhb3DC_n2pWkEbqk5t_5tks5vTWASgaJpZM4DjoGs
.

Hello,

Im looking for a way to add a script to the rendered map to redirect when you click on a marker, however, in order to create the map I'm using map.save('map.html'), I have tried to use the recommendation above but I received the same error message AttributeError: 'Map' object has no attribute 'create_map'

Thanks in advance

@FabeG do you want to open a PR? We can discuss it there further. Two things already:

Do we need the html argument? It's a javascript class. It makes the __init__ simpler to remove it, only pass javascript text.

I think the current approach with the arguments can be better. You let them pass a args dictionary, but we could use Pythons **kwargs syntax instead. I'm torn between:

  • letting the user do the string formatting before passing. E.g. Javascript('my_js = {m};'.format(m.get_name()))
  • having the user pass folium objects as **kwargs and using get_name() under the hood in the class. E.g. Javascript('my_js = {{ m }};', m=my_map); and then pass m.get_name() to render().

The first is obviously the most simple and fool-proof. All users know string formatting, not that many may know Jinja2 template language. But the second approach relieves the user from having to use get_name().

@Delvio the create_mapmethod doesn't exist anymore. If you could provide an example of what you try to achieve, maybe somebody can help you finding a solution.

@Conengmo Thanks for your answer and advice.

  • Regarding the html(and script) arguments: the idea was to be able to add raw html snippet in the <body> part of the output file (for example <div> tag elements to include legend on the map). But maybe it can be done by another type of Element in folium I am not aware of
  • Regarding the two options, I have a slight preference for the second: the link between the javascript part and the python world is maybe more clearly exposed.

I will try to propose a PR in order to continue the discussion.

@FabeG ,
You can try this below, this is what I normally do to include a custom JS in a folium map html.

from branca.element import Element
m = folium.Map()
map_id = m.get_name()

my_js = """var lastZoom;
     {0}.on('zoomend', function() {
    var zoom = {0}.getZoom();
if (zoom < tooltipThreshold && (!lastZoom || lastZoom >= tooltipThreshold)) {
    {0}.eachLayer(function(l) {
        if (l.getTooltip()) {
            var tooltip = l.getTooltip();
            l.unbindTooltip().bindTooltip(tooltip, {
                permanent: false
            })
        }
    })
} else if (zoom >= tooltipThreshold && (!lastZoom || lastZoom < tooltipThreshold)) {
    {0}.eachLayer(function(l) {
        if (l.getTooltip()) {
            var tooltip = l.getTooltip();
            l.unbindTooltip().bindTooltip(tooltip, {
                permanent: true
            })
        }
    });
}
lastZoom = zoom;
})""".format(map_id)

e = Element(my_js)
html = m.get_root()
html.script.get_root().render()
# Insert new element or custom JS
html.script._children[e.get_name()] = e

m.save('mymap.html')
Was this page helpful?
0 / 5 - 0 ratings

Related issues

b7j picture b7j  Â·  13Comments

spanishinquistion picture spanishinquistion  Â·  16Comments

ispmarin picture ispmarin  Â·  17Comments

FlorianHoevelmann picture FlorianHoevelmann  Â·  14Comments

nathan3leaf picture nathan3leaf  Â·  32Comments