Skip to content
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

Add crossfilter mode to link_selections #4119

Merged
merged 97 commits into from
Mar 2, 2020
Merged

Add crossfilter mode to link_selections #4119

merged 97 commits into from
Mar 2, 2020

Conversation

jonmmease
Copy link
Collaborator

Overview

This PR introduces a new mode argument to the link_selections function that was added in #4044.

The default value, "overwrite", matches the existing behavior where each selection replaces the prior selection across all elements.

In the new "crossfilter" mode, each selection replaces any existing selection on the current element, and selections across all elements are combined with an intersection operation.

To make the crossfilter state easier to interpret, the selected region for each element on which a selection has been performed is displayed in a lighter shade of the selection color. For box-select on scatter-like 2D elements, the selection region is displayed as a Bounds element. For histogram elements, the selected region is displayed as an overlayed histogram indicating which bars have been selected.

See the GIFs below for examples of what this looks like. Do these selection region indicators make sense to others?

Additionally, this PR uses the PlotReset stream to reset all selections when the plot is reset. This works for the bokeh backend, but the PlotReset stream is not yet implemented for the Plotly backend.

Examples

Example notebook at https://anaconda.org/jonmmease/cross_filter_link_selections_pr

Bokeh scatter/histogram example

crossfilter1

Plotly scatter/histogram/scatter3d/table example

crossfilter2

Bokeh violin/bivariate/distribution/histogram example

crossfilter3

Bokeh datashader example

crossfilter4

Glaciers Example

Note: I believe this functionality is enough to rewrite the glaciers app once #4116 is fixed.

@jbednar
Copy link
Member

jbednar commented Dec 5, 2019

Cool! Note that the behavior here is a bit different from in the Glaciers app; for Glaciers subsequent selections always act on the already selected subset, even if done in the same element as previously selected. E.g. if selected on the map view, a subsequent selection on the map view selects the intersection between the current selection and the new selection; it doesn't replace the old selection.

What's implemented here seems like it would often be more useful, particularly as it displays what the old selection is, but maybe that is an additional mode option?

@jonmmease
Copy link
Collaborator Author

Note that the behavior here is a bit different from in the Glaciers app

Ahh, ok, thanks for pointing that out. That behavior is actually a little easier to implement since you don't need to track the source of the selections, but I personally find it to be a bit disorienting.

but maybe that is an additional mode option?

That's a possibility. A full generalization here would be to have separate options for the within element operation (overwrite, intersection, union) and the cross element operation (overwrite, intersection, union).

But, to do this well would take a bit of rework I think. Displaying the selected region in the presence of union/intersection operations would require some shapely-style operations to merge polygons (which we would probably want to implement in spatialpandas). And, we may want to define a higher-level selection object. Right now, selections are represented as dim expressions. But to support efficiently applying geometry operations on selections, I think it would be better for selections to be geometry-like objects that know how to produce dim expressions.

All that said, if we want to merge this mode="crossfilter" support now, we would still be a able to support this API in the context of the enhancements above. At that point mode would become a high-level specification of common interaction modes that would be implemented in terms of these lower-level constructs. So maybe mode="crossfilter" would be equivalent to something like within_op="overwrite", between_op="intersection".

@jbednar
Copy link
Member

jbednar commented Dec 5, 2019

Displaying the selected region in the presence of union/intersection operations would require some shapely-style operations to merge polygons

I'm inclined to be less ambitious, and would be happy with either not showing the selection bounding box in the within_op="intersection" case, or just showing all the selected regions (letting the user see that the actual selection is the intersection of the multiple bounding boxes shown). And in general being able to turn off showing the selection box would be useful in some cases.

@jonmmease
Copy link
Collaborator Author

Ok, I've replaced mode with three orthogonal args: element_op, cross_element_op, and show_regions. The example notebook above has been updated with the new syntax.

New default: show_regions=True, cross_element_op="intersect", element_op="overwrite"
crossfilter1

element_op="intersect", cross_element_op="intersect"
crossfilter2

element_op="intersect", cross_element_op="intersect", show_regions=False
crossfilter3

element_op="difference", cross_element_op="overwrite"
crossfilter4

@jbednar
Copy link
Member

jbednar commented Dec 19, 2019

Fabulous! Is there anything preventing the Glaciers app from being rewritten to use this now?

@jonmmease
Copy link
Collaborator Author

Is there anything preventing the Glaciers app from being rewritten to use this now?

I don't think so, I'll try it again later today or this evening. in the process, I'll confirm that #4116 is fixed on master.

@jonmmease
Copy link
Collaborator Author

I ran into some issues while porting the glaciers example. Here are PRs that address these: #4137, holoviz/geoviews#414.

With these PRs, I can get pretty close!

@jonmmease
Copy link
Collaborator Author

Here is a port of the glaciers example to this PR (combined with #4137, holoviz/geoviews#414): https://anaconda.org/jonmmease/glaciers_link_selections

It's a lot shorter and simpler I think!

The main difference is the change from displaying the glaciers on the map with rasterize and a custom color scale, to using datashade and letting link_selections control the color scale. Also, right now link_selections only uses BoundsXY and box_select when dealing with Histograms, whereas the current glaciers example uses BoundsX/xbox_select for the histograms. I think this would eventually make sense as an option link_selections.

Here's some example usage:
glaciers_link_selections

@jbednar
Copy link
Member

jbednar commented Dec 20, 2019

Yes, that's much, much simpler! BoundsX and BoundsY are definitely clearer when they are appropriate, such as in this case, so it would be nice to support that.

@fmaussion, can you check out the new notebook and see what you think?

@fmaussion
Copy link
Contributor

@fmaussion, can you check out the new notebook and see what you think?

I can't test it right now, but if I understand well this allows to make selections withing selections, which is nice! I'm happy to have a look at the updated app on https://github.com/pyviz-demos/glaciers when it is adapted

@jbednar
Copy link
Member

jbednar commented Dec 22, 2019

Here the interesting part is not the behavior, but the code -- it should now be possible to make something like your original dashboard with almost no code. So the task is to look at the behavior, but also the code and how they relate. I'll put up a running version once the code is merged and available in a dev release.

@philippjfr
Copy link
Member

@jonmmease, is this ready to review and merge?

@jonmmease
Copy link
Collaborator Author

@jbednar asked me to take a look at having selections preserve the original color(scale) of elements and I haven't had a chance to look at that yet.

If we want to change this behavior now, then we should probably hold off. If we want to keep this approach and add something new later then I think it's ready for review.

@jonmmease
Copy link
Collaborator Author

jonmmease commented Dec 31, 2019

I've made several updates:

  • The default behavior is now for the selected element layer to have the original color of the element and for the unselected layer to be gray. A specific selection color is still available as well.
  • The initial state is now for everything to be selected, so you don't see the gray background until a selection is performed.
  • Added support for elements that are have a cmap style property. This was needed to support the result of rasterize.
  • The selection region, when enabled, is now always a darker shade of the background color. This is a lot simpler, and I think it looks better, especially since there is not necessarily a single selection color anymore.

Basic

crossfilter1

Plotly

crossfilter2

Distributions

crossfilter3

Datashader

crossfilter4

Glaciers

glaciers_link_selections

This example can now keep the original approach of rasterizing glaciers with a custom colormap and colorbar.

@jonmmease
Copy link
Collaborator Author

Looking into the failing tests...

@jbednar
Copy link
Member

jbednar commented Dec 31, 2019

Looks fabulous! People can now enable cross filtering with essentially zero changes to their original plotting code and styling, giving really useful functionality with no cost!

My vote is for the unselected regions to be about half as visible as they are now, i.e. for a white background for them to be a much lighter gray. Right now I find the unselected regions to be a bit distracting; I think they should literally fade into the background a bit, as they are for comparison and context not for close study. Maybe make what is currently dark gray be the same color as what is currently medium gray, and make what is currently medium gray be half as dark as it is how? @philippjfr may also have opinions here.

@jbednar
Copy link
Member

jbednar commented Dec 31, 2019

@philippjfr, can you think about how this relates to gridmatrix (e.g. http://holoviews.org/gallery/demos/bokeh/iris_density_grid.html)? Does the interlinking in gridmatrix now reduce to simply calling this method? (Though gridmatrix only requires JS, right?)

@jbednar
Copy link
Member

jbednar commented Dec 31, 2019

BTW, in the Datashader example above, the selected Datashader plot appears to be drawn on top of the selection outline?

image

Presumably it should be the other way around?

@philippjfr
Copy link
Member

Gridmatrix interlinking is all client side and therefore comes for free anyway.

@jbednar
Copy link
Member

jbednar commented Dec 31, 2019

Gridmatrix interlinking is all client side and therefore comes for free anyway.

We still have to maintain that code, but of course we'll do so since it provides client-side (exportable) functionality, which is complementary. What I'm asking is whether (apart from client-side or server-side considerations) the gridmatrix functionality is now a subset of this general crossfiltering functionality? If so that makes things easier to talk and reason about. (And of course to someday hope for full client-side cross-filtering support for the cases where all data is in the client.)

@philippjfr
Copy link
Member

There is no code associated with that, we get it for free from bokeh. I'm not sure how to answer your question though. The difference is that bokeh exposes selection/nonselection options to control the styling of each. Additionally the UI is different in that you can apply multiple selections using shift select. So I wouldn't say they are subsets.

@jonmmease
Copy link
Collaborator Author

@jbednar
In 6e1064e I updated the ordering of the selected regions so that the box outline is drawn on top.

In 6f8c969 I lightened the shades of gray used for the unselected layers.

Here's how things look now:
Screenshot_20200101_105031

@philippjfr, when you have a chance, could you take a look at ac4025b? My goal here was to ensure that rasterize.instance(**kwargs)(element) is always equivalent to rasterize(element, **kwargs). This wasn't always true before, because in the former case the kwargs did not get propagated to the rasterization implementations.

@jbednar
Copy link
Member

jbednar commented Jan 1, 2020

Thanks! The darker gray still looks too dark to me; I'd want it to visually group with the lighter gray, because they are both actually the background and not the main point of the plot anymore. I.e. it should be distinguishable from the lighter gray, but not dramatically different, so that together the grey bits fade into the background, while the foreground is quite clearly distinct. The coloration would need to be adjusted for the map plot in the Glaciers example, where fading into the background requires darker colors and not lighter ones, but that's an unusual case and it's fine for it to require custom styling when that occurs (i.e. when one pane in a layout has a dark background and the rest don't).

@jonmmease
Copy link
Collaborator Author

As things stand right now, it's not possible to configure separate unselected colors per plot or plot type. So the gray for the selected histogram bars matches the gray for the selection boxes. I think it would work fine to have the histogram color lighter than the box color, but I can't picture a clear API at the moment for configuring separate colors per subplot.

@jonmmease
Copy link
Collaborator Author

In 442eea8 I updated things so that selected histogram bars are just slightly darker than the unselected bars, while keeping the selected boxes a darker shade.

I also gave the box lines width 1 (instead of 2) to hopefully be a bit less distracting. Here is the updated example. Note that the bottom left histogram was created with a white line color.

Screenshot_20200102_104312

@philippjfr
Copy link
Member

That looks good to me. Should we also expose the alpha of the selected and unselected elements?

@jonmmease
Copy link
Collaborator Author

Should we also expose the alpha of the selected and unselected elements?

Yeah, I suppose we could. Though I think I'd prefer not to block this PR on that.

@jbednar
Copy link
Member

jbednar commented Jan 2, 2020

That looks good to me too, thanks! I'm happy with it, but just as an idea -- would it make sense to have a fixed shade of gray for the background bits and use alpha changes between the selected and unselected ones rather than different shades of gray, so that they would automatically blend into the background (becoming darker when the background is dark, and lighter when the background is light)? That might alleviate or eliminate the issue with the map pane for the Glaciers example.

@jonmmease
Copy link
Collaborator Author

would it make sense to have a fixed shade of gray for the background bits and use alpha changes between the selected and unselected ones rather than different shades of gray, so that they would automatically blend into the background...?

Yeah, that's a good idea and certainly something worth experimenting with.

@philippjfr
Copy link
Member

Would you want to merge this now and follow up with that later? Hoping to release hv 1.13.0 containing all of this work early next week.

@philippjfr philippjfr merged commit 6f9ed6a into master Mar 2, 2020
@jbednar
Copy link
Member

jbednar commented Mar 2, 2020

Yay!

@philippjfr philippjfr deleted the selection_mode branch April 25, 2022 14:42
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants