Skip to content

First draft of new custom pane functionality. #1094

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

Merged
merged 9 commits into from
Apr 14, 2019

Conversation

lpkirwin
Copy link
Contributor

@lpkirwin lpkirwin commented Mar 14, 2019

This is a rough sketch to agree the API before I flesh it out and add docs. Right now I see two options for the API:

  1. Add custom pane object to map, add kwarg to other layers:
CustomPane("name", ...).add_to(m)
OtherLayer(..., pane="name).add_to(m)
  • Closest to leaflet API
  • Would require 'pane' argument or **kwargs for layers (raster layers already pass kwargs, and leaflet layers all take 'pane' as an option
  • I haven't changed any other class yet, but the example in the notebook works because TileLayer does pass kwargs on to leaflet
  1. Add custom pane object to map, add other layers as child of pane:
p = CustomPane(...).add_to(m)
OtherLayer(...).add_to(p)
  • Don't have to mess around with extra arguments to the other layers
  • Further from the leaflet API, but perhaps (?) closer to folium?

What I have so far is the basis for 1. Happy to flip to 2 if that's preferred.

Closes #1088

@lpkirwin
Copy link
Contributor Author

#1088

@Conengmo
Copy link
Member

Conengmo commented Mar 16, 2019

Hi @lpkirwin, thanks for taking this up. Good sketch you made. It is clear to me how option 1 would work out.

I have a preference for option 2 because:

  • it's more like folium
  • we can do more checks on the Python side and raise exceptions there

But I'm not sure if we can implement that in a sensible way. Leaflet has no methods to move an object to another pane, it has to be done on creation as far as I can tell. So we would have to rummage about in the templates or options of folium objects anyway.

I'd be interested to hear your ideas about how we could implement option 2.

If that seems not workable, option 1 is also a good option. Passing all **kwargs as Leaflet options for most folium objects is something we wanted to do anyway. Perhaps we can find another way to raise meaningful exception on the Python side when users implement this functionality wrong.

@Conengmo Conengmo added the in discussion This PR or issue is being discussed label Mar 16, 2019
- Layers now added as the child of a pane
- Options and parent of child layers overridden at render time
- Avoids the need to add a kwarg to other layers
@lpkirwin
Copy link
Contributor Author

Hi @Conengmo, no problem.

Added a new commit with an idea for option 2. It follows the second API above, adding other layers as children to the custom pane. I've added a render argument to the CustomPane class that

  • Adds the 'pane' option to each child's options dict
  • Switches the parent of each child to the pane's parent (i.e. the map)

and then calls super(). Works fine with TileLayers, e.g. here. I can see not all folium classes have options, so not sure how well this would extend past TileLayers (and unsure how many use-cases there are outside TileLayers...)

What do you think?

folium/map.py Outdated
options = json.loads(element.options)
options.update({'pane': self.get_name()})
element.options = json.dumps(options, sort_keys=True, indent=8)
element._parent = self._parent
Copy link
Member

Choose a reason for hiding this comment

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

When you move the element up the tree it will not get rendered, because the parent of CustomPane has already been rendered...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It rendered OK in my test notebook, see screenshot below. I haven't properly moved the element up the tree -- just swapped out the parent, but the tile layer still exists in the pane's children list.

image

Copy link
Member

@Conengmo Conengmo Mar 16, 2019

Choose a reason for hiding this comment

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

Ah right, it's in the list of children so it will get rendered. Good one.

But then why change its parent?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the parent just so that, when render is called on the layer, the javascript ends up as

element().add_to(map_name)

rather than

element().add_to(pane_name)

which leaflet wouldn't understand

Copy link
Member

Choose a reason for hiding this comment

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

That makes sense, thanks for explaining.

@Conengmo
Copy link
Member

Thanks for the quick test. I added a comment about a possible problem with the child elements not being rendered. That's probably fixable by calling render manually. In general I think it best to avoid messing with the object tree. But sometimes it has to be done to get cool features :)

What do you think is the best approach of these two?

@lpkirwin
Copy link
Contributor Author

I like the API better on the second, but same as you I worry that hacking around in the object tree could have some unintended consequences, e.g. when interacting with other objects or features that might not expect this behavior. After calling render() on the pane, there's some inconsistent state (element has the map as a _parent, but is in the pane's _children and not the map's _children).

But I don't have other concrete ideas at the moment. Maybe it would be possible for the tile layer to check at render time whether its parent was a map or a pane, and then use its parent/grandparent name as appropriate.

@Conengmo
Copy link
Member

Hmm given your exploration I now lean towards option 1. It seems less fragile than mucking around with the object tree.

I'm currently working on making sure each object passes **kwargs as options, so that should work together nicely.

@lpkirwin
Copy link
Contributor Author

Sounds good, and I agree. I'll revert back to option 1 and flesh out the documentation

@lpkirwin
Copy link
Contributor Author

Hi @Conengmo, have switched back to the earlier implementation and added a docstring and a bit more to the notebook. Couple questions for you:

  • Should I do anything about kwargs, e.g. adding 'pane' as a specific kwarg in other classes, or adding **kwargs to classes that don't accept them? You mentioned that this was being developed elsewhere, so if so I can hold off.
  • A straightforward test to add would be the equivalent of test_popup_show() in test_map.py. Do you think any other tests would be helpful?

Copy link
Member

@Conengmo Conengmo left a comment

Choose a reason for hiding this comment

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

This is looking good! I added some smaller comments to put the dots on the i so to say, hope you can have a look at them.

Should I do anything about kwargs, e.g. adding 'pane' as a specific kwarg in other classes, or adding **kwargs to classes that don't accept them? You mentioned that this was being developed elsewhere, so if so I can hold off.

That PR has been merged, so most classes accept options in kwargs. We could consider adding pane as an explicit argument to some classes, but given that this is an advanced feature I reckon we can keep it as it is now.

A straightforward test to add would be the equivalent of test_popup_show() in test_map.py. Do you think any other tests would be helpful?

Yes, something like test_popup_show() would be fine! We want to make more functional tests later, but for now having a test that verifies stuff didn't got broken basically is already very useful.

(625) corresponds to between markers and tooltips. Default
panes and z-indexes can be found at
https://github.com/Leaflet/Leaflet/blob/v1.0.0/dist/leaflet.css#L87
pointer_events: bool, default False
Copy link
Member

Choose a reason for hiding this comment

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

What are the consequences of setting the default here to False?

Maybe good to add a small section in the example notebook on how this option can be used.

Copy link
Contributor Author

@lpkirwin lpkirwin Apr 5, 2019

Choose a reason for hiding this comment

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

For my particular use case (labels on top of choropleth), setting this to False ensures that the new layer won't mask mouse events from lower layers, e.g. if you want a tooltip to pop up when you hover over a choropleth.

Right now I'm struggling to imagine a case where you'd want this to be True! Have googled around for how others are using custom panes, and none of them involve interactivity. I could remove the keyword from the class -- I put it in thinking it would be better to keep flexibility even if I can't anticipate how it would be used.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for explaining, sounds like False is a good default. I agree on leaving it in.

@Conengmo Conengmo added waiting for changes This PR has been reviewed and changes are needed before merging and removed in discussion This PR or issue is being discussed labels Mar 31, 2019
lpkirwin added 3 commits April 5, 2019 22:08
… into custom-panes

Pulling latest changes from master
- Cleaning up the jinja template
- Changing link in the docstring
- Adding a test for correct script representation
- Adding a couple more tile templates to the standard set
- Changing python version to match other notebooks
- Removing TOC section from nbextension
@Conengmo
Copy link
Member

Just had a look at your changes and I think this is ready! The code looks nice, you wrote a test, the example notebook is good.

We fixed the issue with the notebook tests on Travis before, so I took the liberty of updating your branch from master. When the tests are passed I'll go ahead and merge this.

@Conengmo Conengmo added ready PR is ready for merging and removed waiting for changes This PR has been reviewed and changes are needed before merging labels Apr 14, 2019
@Conengmo Conengmo merged commit 3ad7a28 into python-visualization:master Apr 14, 2019
@Conengmo Conengmo removed the ready PR is ready for merging label Apr 14, 2019
@lpkirwin
Copy link
Contributor Author

Just had a look at your changes and I think this is ready! The code looks nice, you wrote a test, the example notebook is good.

We fixed the issue with the notebook tests on Travis before, so I took the liberty of updating your branch from master. When the tests are passed I'll go ahead and merge this.

Thanks Frank!

@lpkirwin lpkirwin deleted the custom-panes branch April 15, 2019 14:01
@jmaralc
Copy link

jmaralc commented Aug 12, 2019

Great feature!! thanks a lot 👍

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.

Support for custom map panes
3 participants