Closed hhoeflin closed 4 years ago
Thanks! I have actually just started looking into it last weekend, but there is still some work to do. Are there any particular features that you need?
Thanks for the quick answer. Straight GeoJSON, maybe with the ability to specify color, opacity, inner color, line color, line thickness, etc for each feature. When looking into leaflet, there seems to be the "style" property which however expects a function, so I guess this would have to be mapped to some other way of handling it for dash?
On Wed, May 6, 2020 at 12:25 PM Emil Haldrup Eriksen < notifications@github.com> wrote:
Thanks! I have actually just started looking into it last weekend, but there is still some work to do. Are there any particular features that you need?
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/thedirtyfew/dash-leaflet/issues/14#issuecomment-624565638, or unsubscribe https://github.com/notifications/unsubscribe-auth/AASGMYVEVQEU3WM3WQSTD73RQE3IRANCNFSM4M2JKNTQ .
Yes, that is in fact one of the key challenges in writing the geojson wrapper. AFAIK, there is no direct way of passing function handlers to the javascript layer. But i would love to be proved wrong :)
My current design approach is to create an options
dict, which holds a style
dict (among other things). The user can then set an options
dict on the geojson level, which will be the default for all features. Additionally, a featureOptions
dict with the feature id as key and a options
dict can be used to assign options, such as the style, for each feature. Here is a small example demonstrating the current syntax,
import json
import dash
import dash_html_components as html
import dash_leaflet as dl
with open("assets/us-states.json", 'r') as f:
statesData = json.load(f)
def get_color(d):
color_limits = {1000: '#800026', 500: '#BD0026', 200: '#E31A1C', 100: '#FC4E2A', 50: '#FD8D3C', 20: '#FEB24C',
10: '#FED976', -1: '#FFEDA0'}
for key in color_limits:
if d > key:
return color_limits[key]
def get_style(d):
return dict(fillColor=get_color(d), weight=2, opacity=1, color='white', dashArray='3', fillOpacity=0.7)
# Bind per-feature style information.
featureOptions = {}
for item in statesData["features"]:
featureOptions[item["id"]] = dict(style=get_style(item["properties"]["density"]),
popupContent="{:.3f} people/mi2".format(item["properties"]["density"]))
# Create geojson.
options = dict(hoverStyle=dict(weight=5, color='#666', dashArray=''), zoomToBoundsOnClick=True)
geojson = dl.GeoJSON(data=statesData, id="geojson", options=options, featureOptions=featureOptions)
# Create app.
app = dash.Dash()
app.layout = html.Div([
dl.Map(children=[dl.TileLayer(url="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png")] + [geojson],
style={'width': "100%", 'height': "100%"}, center=[39, -98], zoom=4, id="map", maxZoom=18),
], style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block"})
if __name__ == '__main__':
app.run_server()
The us-states.json file contains the data used in the Leaflet choropleth guide,
https://leafletjs.com/examples/choropleth/
If you would like to try it out, the preliminary component is available in dash-leaflet==0.0.10rc2, and you can download the json data here. If you have any suggestions to a better design approach and/or syntax, please let me know.
good to hear that this is on the roadmap. I also don't have easier options. But from looking at the examples of functions for "style" presented in leaflet, they usually show how to map something from a feature property. Like in the answer to this example: https://gis.stackexchange.com/questions/158227/leaflet-geojson-problem-with-style
So what I thought that there could be a default dict with default values, and maybe a "mapping" that defines which feature properties should be used for which style-values (then you would natively use values specified in the geojson, and don't have to style it from the outside using ids.
Happy to talk more!
One advantage with the suggested mapping approach would also be that it would then be possible to update the 'data' property. As the styling would be a pre-configured styling function (with the mapping as a bound parameter), the reset geo-json data would be styled correctly.
Using the options approach, the 'data' and the 'options' prop would always have to be reset at the same time, otherwise features with unknown ids would not be styled correctly.
My first implementation was along the lines that you are suggesting, i simply looked for a "style" property on each feature and applied the style if it was there. However, i didn't like this approach for two main reasons,
1) In most cases, the style is not part of the original geojson data. Hence you would have to create the styles in Python and add them to the geojson data prior to creating the GeoJSON object for Dash Leaflet. While this is perfectly possible, i find the approach of separation of data and style in separate properties (the current implementation) more clean; the data is provided as-is and the style is calculated on the side. On the other hand, the linking using ids is not a perfect solution from a syntax point of view, so if it has not been for (2) i would probably have kept this implementation. The syntax issue could maybe be helped somewhat be hiding the id mapping behind a convenience wrapper function in Python.
2) If the style is provided as part of the GeoJSON object, it is not possible to update the styles without updating the data property. Hence if you have a large geojson file loaded on the map, and you want to change the color of a feature from Python, you would have to send the complete geojson file from the server to the client again. In some cases, i imagine that this would be a deal breaker. By separating the data and style in two properties, it would be possible only to send the styles across the wire, which might be significantly faster in some cases.
Could you elaborate what you mean by a "pre-configured styling function"? To my knowledge it is not possible to pass functions from Dash to javascript. But if there is some way, that would be great; it would simply things a lot :)
Sure - and please excuse me if this makes no sense. I have little to no javascript-knowledge, so maybe this doesn't work.
As far as I understand it, it is correct that dash cannot pass a function. What I think would be an option is to add an additional prop to leaflet (e.g. _stylingmapping, which when changed triggers a change in the style prop-function, in javascript. This styling function is implemented in javascript directly, and takes as parameters the 'feature' as well as a 'styling_mapping' and then just returns the feature-properties with the remapped names as defined in 'styling_mapping'.
I can only do this in equivalent python-code, which would be
def my_style(feature, style_mapping):
output = {}
for key, value in style_mapping.items():
output[key] = feature.properties[value]
return output
and with a change of the _stylemapping prop, the style prop gets reset to
partial(my_stile, style_mapping=style_mapping_prop)
Hope this makes sense. More sophistication could be added to automatically emit a default value if the requested property happens to be absent.
Furthermore, I agree with your reasoning, I can see good applications for both approaches. For me, I do create my geojson data on the fly in a server. I can easily add the styling information. But I find your explanation perfectly reasonable.
Another maybe a bit whacky idea would be to allow sending a true styling function as a string into a prop, converting the string to a javascript function on the javascript side and then set this into the 'style' prop. Then things like
function(feature) {
switch (feature.properties.party) {
case 'Republican': return {color: "#ff0000"};
case 'Democrat': return {color: "#0000ff"};
}
}
should be possible. Does this work?
It makes complete sense - the logic your are suggesting is conceptually similar to what I am doing in the Javascript layer ;)
The main drawback of your suggested solution, as compared to a separate style property, is that all styles must be computed in advance. This is a problem if e.g. a polygon color depends on a user input; as I understand, your approach would then require the whole geojson data to be sent from server to client each time the user input changes.
Haha, I was also thinking about just passing JavaScript function strings. While this is definite possible (Javascript has an eval function; it evaluates strings as code), it would be kind of hacky IMO, and it would require the user to write Javascript (which I guess a lot of Dash users would not appreciate).
A variation of this option could be to allow the user to specify a JavaScript function in an external file; this would make it easier to write the code (you can use an IDE), and less hacky since the eval function is not required. However, it still requires JavaScript knowledge. Therefore, I think this would only be a viable as an optional option; to enable all Dash users to use the component, there should be a Python-only option available.
Yeah, I agree with the hackyness of javascript functions as strings. Although now there is precedent - https://dash.plotly.com/clientside-callbacks
Clientside-callbacks as implemented in dash are nothing but javascript functions as strings in python and passed to the client. So this would just be a simple extension of the clientside-javascript function paradigm. And I agree it would not be appreciated if it was the only way to style it. But what if it was an additional option. Maybe your "id" idea through the options, and alternatively a javascript function could be passed as string (the latter overriding the former).
I have decided not to pursue the java script approach for now, it might be added later as an option. Instead, i have created a new express
module, which holds convenience functions, one of which addresses the issue discussed here. Hence, you can now construct a GeoJSON object like this,
from dash_leaflet import express as dlx
....
def get_style(feature):
color = get_color_somehow(feature)
return dict(fillColor=color, weight=2, opacity=1, color='white', dashArray='3', fillOpacity=0.7)
geojson = dlx.geojson(data, style=get_style)
It is thus possible to use a syntax that follows the leaflet component closely, even though the implementation is somewhat different.
Thanks, that looks pretty good! And then I pass the whole geojson object into the leaflet layer, correct?
Yes, in the code above ‘data’ is the raw geojson data, and the ‘geojson’ variable is a GeoJSON object, which can be added to the map (like layers, markers, etc.)
Hi. nice feature. How to make use of this new feature in callbacks, how to get clicked state id or state name?
Thanks! I have just released version 0.0.11 which includes the GeoJSON component. It supports click events and hoover events via the properties featureClick
and featureHover
.
I have just created a documentation page, which container a number of examples including one demonstrating the usage of the new GeoJSON component,
great. good idea. are you planning to post all the docs what you create.
I intend to keep the documentation up to date with the main features. At some point i might also add an API doc, but until then, you can see the documentation of the individual component properties in the Python source files (in PyCharm you can just to the component source by clicking "ctrl"+"b", other editors probably have similar functionality).
This does not seem to work anymore. I'm using version 0.1.13, has the usage changed?
Could you elaborate on what is not working? And what versions are you comparing? There has been significant development on the component since this issue was created.
This does not seem to work anymore. I'm using version 0.1.13, has the usage changed?
There is no featureOptions argument that I found either.
Yes, the syntax changed from 0.1.0 and onwards :). And yes, it is possible to apply different colors per feature. It requires writing a small JavaScript function though.
Yes, the syntax changed from 0.1.0 and onwards :). And yes, it is possible to apply different colors per feature. It requires writing a small JavaScript function though.
Thank you, I figured out the js script that was needed!
Hello, I am new to dash leaflet. I have the same Celtics33's problem . What .js do I need to have? and where should I put it? in the assets folder?
Thanks
@yampi67 Yes, you can put it in the asserts folder. Or you could link it as an external asset, that is up to you.
Hi,
great package, I love leaflet for its versatility and having it available in dash is great! One of the layers currently not supported is GeoJSON. Any plans to implement? I know time is limited ...
Thanks!