python-visualization / folium

Python Data. Leaflet.js Maps.
https://python-visualization.github.io/folium/
MIT License
6.94k stars 2.23k forks source link

Add example pattern for pulling JsCode snippets from external javascript files #1942

Closed thomasegriffith closed 6 months ago

thomasegriffith commented 7 months ago

Is your feature request related to a problem? Please describe. I have found myself embedding a lot of javascript code via JsCode when using the Realtime plugin. This gets messy for a few reasons:

I'm wondering if there's a recommended way to solve this.

Describe the solution you'd like A solution would allow one to reference specific functions from external javascript files within a JsCode object.

It's not clear to me what would be better: whether to do this from within folium (using the API) or to use some external library to populate the string of javascript code that's passed to JsCode based on some javascript file and function. In either case, having an example in the examples folder would be great.

Describe alternatives you've considered If nothing is changed about the folium API, this could just be done external to folium. As in, another library interprets a javascript file, and given a function name, the function's definition is returned as a string. Is this preferable to building the functionality into folium? If so, does anybody know of an existing library that can already do this?

Additional context n/a

Implementation n/a

hansthen commented 7 months ago

Very interesting idea. I have noticed the same drawbacks of having javascript as strings inside python. (And I would add there is no editor support or linting of javascript from inside python).

I don't know of a library that does exactly this, but there are javascript parsers available in python. E.g. https://github.com/PiotrDabkowski/pyjsparser. And there is pythonmonkey. This serves a totally different purpose, but also does javascript parsing.

I think with either we could hack up a PoC that does what you want. Do you have an example of how you would want to use this library to be?

thomasegriffith commented 7 months ago

Agreed: lack of editor and linting support are also painful.

Do you have an example of how you would want to use this library to be?

Maybe something like:

pointToLayer.js

function pointToLayer(f, latlng) {
    ...
}

plugin.js

function somePlugin() {
    ...
}

folium.py

point_to_layer_function = ExternalJsFunction(file="/path/to/foo.js", function="pointToLayer")
some_plugin_function = ExternalJsFunction(file="/path/to/plugin.js", function="somePlugin")

# A list of functions is passed to `point_to_layer`
realtime_plugin = folium.plugins.Realtime(..., point_to_layer=[point_to_layer_function, some_plugin_function])
hansthen commented 7 months ago

Would a preprocessor be acceptable? Something that takes as input a javascript module and generates the required python code?

thomasegriffith commented 7 months ago

Maybe. What would the usage look like?

hansthen commented 6 months ago

I created a small PoC in javascript that creates JsCode definitions for each function inside a javascript module.

It creates a file example.py like this:

from folium.utilities import JsCode
hello = JsCode("""() => { console.log('hello, world') }""")
toString = JsCode("""(fn) => { return fn.toString() }""")

From python you would be able to use this as follows:

import example

Realtime(
    url,
    on_each_feature=example.hello,
).add_to(m)
thomasegriffith commented 6 months ago

That's pretty cool! Seems useful to me.

Can you post the javascript module too? It's defining non-lambda functions hello and toString that later get mapped to lambdas in the generated python?

hansthen commented 6 months ago

This is the code I used for the PoC. It needs to be tweaked to accept arguments.

module = require("./bye.js")
console.log("from folium.utilities import JsCode")
Object.entries(module).forEach(([k,v]) => {
   console.log(`${k} = JsCode("""${v.toString()}""")`);
});

This is the code for bye.js. I based it on a PythonMonkey example. I think the crucial part is that the function names need to be exported from javascript somehow. The PythonMonkey example used direct assignments to exports, which does not feel natural to me.

exports.hello = () => { console.log('hello, world') };
exports.toString = (fn) => { return fn.toString() };
hansthen commented 6 months ago

@thomasegriffith Is this sufficient for you? If you are okay with it, I'd like to close the issue.

I don't think the example I wrote is sufficiently mature to be included in the Folium core library (even as a documentation.) I checked with the PythonMonkey development team. They have plans to make javascript source code directly accessible. We can revisit this issue when that happens.

thomasegriffith commented 6 months ago

Sounds good @hansthen. Thanks for the discussion and example!!