holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.69k stars 402 forks source link

Drilldown support #5884

Open jbednar opened 12 months ago

jbednar commented 12 months ago

(I could have sworn we had a several-year-old issue about this, but if so I can't find it!)

From my observations, many of what people consider "custom" dashboards are really just trying to show how multiple dimensions of data relate to each other, given the limitations of a 2D computer screen. Linked selections addresses some of the issues, letting you put up various plots of q vs t, h vs g, g vs w, and relate any given data points to the ones shown in other plots, so that you can see how the various quantities that can be measured correlate or distinguish different samples. Many applications of linked selections would have previously had to be complex logic implemented just for that dashboard, and instead the existing linked selections support can simply implement the general notion of linking related data across separate plots once and for all.

The concept of "drilldown" covers another huge swath of "custom" functionality, and I think that if we can provide general drilldown support in HoloViews, we can vastly reduce the complexity of most of the more-ambitious dashboards that people try to build in Panel. With drilldown, usually a given plot shows some aggregated quantity or some subset of dimensions, and the user selects a particular point, manifold (e.g. a line on an image plot), or selection, and a separate plot is then brought up or updated to show unaggregated data or data from different dimensions. E.g. clicking on a location in a map plot could show a curve of some variable at that location over time, or (as in our LANDSAT example) clicking on an RGB pixel can show the full hyperspectral spectrum of the measurements at that pixel across many different frequency bands.

HoloViz developers have discussed drilldown in the context of many different projects over the years, but we have never properly addressed the topic. Sure, Streams make it relatively easy to set up interactive behavior that connects different plots, but Streams are at a much lower level than I am envisioning, because they require some fairly specific plumbing to connect multiple plots. Maybe Streams are as good as it gets, and if so then we need some detailed examples showing how to solve various drilldown situations. But I think we can do much better than that, with generic support for drilldown where we define one of a few supported types of selection, define what the target is (hovering plot, popup plot, plot updated adjacent to the main one, etc.), and connect everything up in a straightforward way.

E.g. given a plot X and a plot Y, I want something that lets me define some selection on X, map something about that selection to Y, and then either display Y or update it if it is already displayed.

Bokeh's hover support is a good initial target: https://github.com/bokeh/bokeh/issues/13375

image

Can we make it almost as easy to specify such a hovering plot as it is to lay two Elements or Overlays out, side by side? How can we define the connection between those two plots in a general way?

That one goes by points, but by lines is also really useful, providing a cross section on the right sampled across an additional depth dimension for the two lines drawn on the left: https://earthsim.holoviz.org/user_guide/Analyzing_Meshes.html

image

There are many, many more examples that can be added here, and I think supporting easy drill down will provide power to ordinary people that really exploits and demonstrates the value of the HoloViews approach.

ahuang11 commented 12 months ago

Here's an example using hvplot / holoviews. I had to make some internal changes here https://github.com/holoviz/holoviews/pull/5885/files

And I was able to create an example:

import holoviews as hv

from bokeh.plotting import figure
from bokeh.models.tools import HoverTool
from bokeh.models import RendererGroup
from bokeh.models.dom import (
    Div,
    Index,
    Span,
    Styles,
    Template,
    ValueRef,
    ToggleGroup,
)

hv.extension("bokeh")

def timeseries():
    plot = figure(
        width=150,
        height=75,
        toolbar_location=None,
        x_axis_label="Hours Ago",
        y_axis_label="Temperature (F)",
    )
    groups = []
    for station, station_df in df.groupby("station"):
        hours = (station_df["valid"].iloc[0] - station_df["valid"]).dt.total_seconds() / 3600
        tmpf = station_df["tmpf"]
        group = RendererGroup(visible=False)
        groups.append(group)
        glyph = plot.line(x=hours, y=tmpf, line_width=2, line_color="black")
        glyph.group = group
    return plot, groups

def tooltips():
    plot, groups = timeseries()

    style = Styles(
        display="grid",
        grid_template_columns="auto auto",
        column_gap="10px",
    )
    grid = Div(style=style)
    grid.children = [
        Span(),
        Span(children=["#", Index()]),
        "Station",
        Span(style=dict(font_weight="bold"), children=[ValueRef(field="station")]),
        "Longitude", ValueRef(field="lon"),
        "Latitude", ValueRef(field="lat"),
    ]
    return Template(children=[grid, plot], actions=[ToggleGroup(groups=groups)])

import hvplot.pandas
import pandas as pd

df = pd.read_csv(
    "https://mesonet.agron.iastate.edu/cgi-bin/request/asos.py?station=SEA&station=BFI&data=tmpf&year1=2023&month1=9&day1=11&year2=2023&month2=9&day2=12&tz=Etc%2FUTC&format=onlycomma&latlon=yes&elev=no&missing=empty&trace=0.0001&direct=no&report_type=3&report_type=4",
    index_col="valid",
    parse_dates=True,
).reset_index()
df.head()

hover = HoverTool(tooltips=tooltips())
df.hvplot.points(
    "lon", "lat", width=500, height=400, tools=[hover], hover_cols=["station"], tiles=True, geo=True
)

Which outputs:

image

However, a few unresolved issues:

  1. Projecting this onto a map causes the lat/lon to also be in Mercator.
  2. I couldn't get an hvplot version working in timeseries(), even with hv.render(), because I think it requires a single bokeh plot figure.

This works for hovering only one point, but upon hovering on a second point, it displays an empty plot.

import holoviews as hv

from bokeh.plotting import figure
from bokeh.models.tools import HoverTool
from bokeh.models import RendererGroup
from bokeh.models.dom import (
    Div,
    Index,
    Span,
    Styles,
    Template,
    ValueRef,
    ToggleGroup,
)

hv.extension("bokeh")

def timeseries():
    plot = figure(
        width=150,
        height=75,
        toolbar_location=None,
        x_axis_label="Hours Ago",
        y_axis_label="Temperature (F)",
    )
    groups = []
    for station, station_df in df.groupby("station"):
        station_df["hours_ago"] = (station_df["valid"].iloc[0] - station_df["valid"]).dt.total_seconds() / 3600
        group = RendererGroup(visible=False)
        groups.append(group)
        glyph = hv.render(station_df.hvplot(x="hours_ago", y="tmpf", visible=False)).renderers[0]
        plot.renderers.append(glyph)
        glyph.group = group
    return plot, groups

def tooltips():
    plot, groups = timeseries()

    style = Styles(
        display="grid",
        grid_template_columns="auto auto",
        column_gap="10px",
    )
    grid = Div(style=style)
    grid.children = [
        Span(),
        Span(children=["#", Index()]),
        "Station",
        Span(style=dict(font_weight="bold"), children=[ValueRef(field="station")]),
        "Longitude", ValueRef(field="lon"),
        "Latitude", ValueRef(field="lat"),
    ]
    return Template(children=[grid, plot], actions=[ToggleGroup(groups=groups)])

import hvplot.pandas
import pandas as pd

df = pd.read_csv(
    "https://mesonet.agron.iastate.edu/cgi-bin/request/asos.py?station=SEA&station=BFI&data=tmpf&year1=2023&month1=9&day1=11&year2=2023&month2=9&day2=12&tz=Etc%2FUTC&format=onlycomma&latlon=yes&elev=no&missing=empty&trace=0.0001&direct=no&report_type=3&report_type=4",
    index_col="valid",
    parse_dates=True,
).reset_index()
df.head()

hover = HoverTool(tooltips=tooltips())
df.hvplot.points(
    "lon", "lat", width=500, height=400, tools=[hover], hover_cols=["station"], tiles=True, geo=True
)
maximlt commented 12 months ago

Thanks for opening this issue @jbednar, that was on my TODO list for a while but never got to it.

The ability for Bokeh to embed a real plot in a tooltip is interesting. The only problem I see starting with that is that I don't imagine you'd embed yet another plot in the tooltip plot (if that's even possible), while if we are to generalize drilldown I guess we don't want to be limited to drilling down to 1 level but to as many levels as required.

ahuang11 commented 12 months ago

The only problem I see starting with that is that I don't imagine you'd embed yet another plot in the tooltip plot (if that's even possible),

For now, I think it'd still be immensely useful if there's a convenient/easy way to add plots to the tooltip, but I agree that drilling down would be also very good; I imagine that would take a lot more work though.

jbednar commented 12 months ago

Thanks for opening this issue @jbednar, that was on my TODO list for a while but never got to it.

That's funny! At the same time I filed this issue, it reminded me that I needed to chase you about filing whatever issue we had discussed you filing a few weeks ago, so I wrote that down too. I guess "drilldown" was that issue, so I can cross off the chasing task! :-) If you have any notes from our discussion of it at that time, please add them here.

The ability for Bokeh to embed a real plot in a tooltip is interesting. The only problem I see starting with that is that I don't imagine you'd embed yet another plot in the tooltip plot (if that's even possible), while if we are to generalize drilldown I guess we don't want to be limited to drilling down to 1 level but to as many levels as required.

Interesting idea; basically like a browser tab: Click on one point in a plot to bring up a drilldown plot, then click on a point in that plot to drill down further, and so on. Could be implemented and might be useful, but I'm not asking for that! I imagine people would quickly lose track of what they are doing if we keep spawning popups like that. Instead, I was expecting multiple-level drilldown to work more left to right in a layout, not using hover but instead controlling a plot on the right, then clicking on that plot would control a plot to the right of that or below it, etc. That's what I more often see, anyway; hover is for a streamlined simpler case.

maximlt commented 12 months ago

Interesting idea; basically like a browser tab: Click on one point in a plot to bring up a drilldown plot, then click on a point in that plot to drill down further, and so on. Could be implemented and might be useful, but I'm not asking for that! I imagine people would quickly lose track of what they are doing if we keep spawning popups like that. Instead, I was expecting multiple-level drilldown to work more left to right in a layout, not using hover but instead controlling a plot on the right, then clicking on that plot would control a plot to the right of that or below it, etc. That's what I more often see, anyway; hover is for a streamlined simpler case.

Oh I didn't mean that we should implement multiple-level drill-down with popups. Instead I tried to say that starting with the plot embedded in a tooltip approach may not be the right one to design the drill-down API as it's hard to see how more than 1 level can be of any use.

I also expect multiple-level drilldown to work more in a layout, and hopefully more in a Panel layout than in the less flexible HoloViews layout.

That's funny! At the same time I filed this issue, it reminded me that I needed to chase you about filing whatever issue we had discussed you filing a few weeks ago, so I wrote that down too. I guess "drilldown" was that issue, so I can cross off the chasing task! :-) If you have any notes from our discussion of it at that time, please add them here.

In that discussion you shared with me a report that linked to PRs that implemented linked selections in HoloVIews. The description was stating "allow creating new plots based on a selection" and you were wondering whether that was made possible in https://github.com/holoviz/holoviews/pull/4547. Maybe @philippjfr knows better? :)

Simpler Drill-Down Dashboard Options PRs: HoloViews #4362, #4364, #4437, #4481, #4547

Added support for creating new plots determined by selections on existing plots. This support was built on the "link_selections" command added to HoloViews previously, generalizing it significantly so that it can support not just linking subselections between simultaneously visible plots, but to allow creating new plots based on a selection. Lasso support was also added to allow arbitrary-shaped selection of subsets of datasets.

You also shared some notes taken from a discussion with you and Philipp I believe, from some years ago.

Philipp & Jim update Focus on the ability to link/drill down to useful data. Idea: Any view in Panel (particularly plots) should be able to expose to other views in well-defined ways. Ability to reference in Lumen. Example: Holoviews plot and want the current selection to flow to another data source Can publish that info. 2 ways to use: More general, “drill-down” You make a selection and want to drill down. Example: build a dashboard where you have ship tracks and you can select a specific ship and track. You have a separate table ship ID, take filter, filter one of the data sources, and feed into a merge with another data source. Merge that with my table of ship information. What you come up with for that ship, you have the other info: name, origin, picture of the ship, etc. Full flexibility of simple selections, but can also allow it to point to other information about that subject.

And you stated the following goal:

My goal would be to get to the point where we can specify drilldown in Lumen, i.e., that we've captured it at a high enough level that it makes sense to specify that a particular plot in a Lumen dashboard is a drilldown expansion from some selection in another plot. Is that enough for you to synthesize the above info into an issue about drilldown, or do you need me to do that?

jbednar commented 11 months ago

Oh I didn't mean that we should implement multiple-level drill-down with popups. Instead I tried to say that starting with the plot embedded in a tooltip approach may not be the right one to design the drill-down API as it's hard to see how more than 1 level can be of any use.

Right; I think we are in agreement about that. But your dismissal of multi-level popups made me at least consider that possibility, which I had not previously. :-)

Ok, thanks for keeping those notes; I had now indeed lost track of all that! Sounds like it captures what we've been thinking about.

jbednar commented 11 months ago

Another relevant drilldown example we have already (examples.holoviz.org/landsat):

image
jbednar commented 11 months ago

And showing Tap vs. hover comparison, from https://github.com/nasa/EMIT-Data-Resources/blob/main/python/tutorials/Exploring_EMIT_L2A_Reflectance.ipynb:

image
ahuang11 commented 10 months ago

Just wanted to share this additional resource here (using CustomJSHover to access Python dict values) https://discourse.holoviz.org/t/applying-a-function-to-a-hover-tool-tooltip-value/6271/8?u=ahuang11