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

Interacting with Legends & Axes #1657

Open
brylie opened this issue Nov 2, 2016 · 26 comments
Open

Interacting with Legends & Axes #1657

brylie opened this issue Nov 2, 2016 · 26 comments

Comments

@brylie
Copy link

brylie commented Nov 2, 2016

NVD3, and possibly other charting libraries, allow users to filter charts by interacting with the chart legend:

peek 2016-11-02 12-16

This is useful for exploratory analysis, and makes toggling categories very easy.

Feature

Is it planned/possible for vega-lite charts to have interactive legends for filtering chart data?

@kanitw kanitw added this to the 2.0.0-α-4 Interactions milestone Nov 2, 2016
@kanitw
Copy link
Member

kanitw commented Nov 2, 2016

Thanks for asking. This will likely be a part of v2.x release.

@domoritz
Copy link
Member

domoritz commented Nov 2, 2016

Interactions with legends was a bit tricky with vega (at least v2). Has that changed? cc @arvind

@arvind
Copy link
Member

arvind commented Nov 2, 2016

Yes, with Vega 3, axes and legends can now be interacted with. So this should be supported with Vega-Lite 2.0. The open question with interactions, however, is what defaults should be. For example, if a legend is added to the visualization, should it be interactive by default? Should the interaction it supports also extend to interacting with the plotted data points as well? Etc.

@jheer
Copy link
Member

jheer commented Nov 2, 2016

We should definitely try to support these kinds of interaction! Not sure if interactive-by-default is the right way to go, but we would need to be able to provide explicit control in either case.

This relates to the selection project transform. As I understand it, our default design right now is no projection for single/multi point selections, and [x, y] projection for interval selections. For axes and continuous (color gradient) legends, I imagine one could define an interval selection projected to the appropriate field/channel/guide. For discrete legends, we might need to default a single-point or multi-point selection to project just the field(s) backing the legend.

There is also the potential issue that users might just want to be able to (more or less) say: "I want to enable selections on this legend (or axis)" without having to think about the appropriate selection type (interval, etc).

@domoritz
Copy link
Member

domoritz commented Nov 2, 2016

I don't think we want any interactions by default. The problem is that I don't know what channel we would use (e.g. for highlighting) and how we resolve conflicts if the user uses that channel to encode data.

@kanitw
Copy link
Member

kanitw commented Nov 2, 2016

RE: default interaction, let's create a new issue (#1658) to discuss that
since it's going a little off topic here.

@brylie
Copy link
Author

brylie commented Nov 2, 2016

For what it's worth, NVD3 chart legends are interactive by default.

@domoritz domoritz modified the milestones: 2.x.x Features & Patches, 2.0.0-β Interactions Follow-up May 31, 2017
@domoritz
Copy link
Member

We have this on the roadmap for 2.1

@kanitw
Copy link
Member

kanitw commented May 31, 2017

Given that we will follow semantic versioning, it is probably more accurate to say "very early versions of 2.x minor release".

@kanitw kanitw modified the milestones: 2.1? Important Patches, 2.x.x Features & Patches Aug 17, 2017
@kanitw kanitw changed the title Possible to use legend to filter graph data? Interacting with Legends Aug 17, 2017
@kanitw kanitw modified the milestones: 2.x? Important Patches, 2.x Interaction Patches Sep 22, 2017
@domoritz
Copy link
Member

domoritz commented Mar 5, 2018

I think this would be awesome to improve some of the default interactions in pdvega. CC @jakevdp

@domoritz
Copy link
Member

domoritz commented Apr 3, 2018

Here is the Vega example https://vega.github.io/vega/examples/interactive-legend/

@kenklin
Copy link
Contributor

kenklin commented Apr 6, 2018

I need to enhance some of my Vega specs to be more interactive, so I experimented migrating them to Vega-Lite, allowing me to easily add zoom (https://vega.github.io/vega-lite/docs/zoom.html). I was previously only (somewhat) familiar with Vega specs, so this is the first time I've tried Vega-Lite.

My port has been mostly successful with the notable exception of my Vega spec's use of https://vega.github.io/vega/examples/interactive-legend/. After searching, I found this open issue 1657.

Do you have any practical recommendations on how I might proceed? I think my options are:

  1. Shelve my Vega-Lite code and resume development on my Vega code.

  2. Inquire if there is a way to "manually" modify the Vega-Lite spec to create interactive-legend-like behavior. I am guessing this is a dead end as I infer that Vega-Lite does not have signals (https://github.com/vega/vega-lite/milestone/55).

  3. Try compiling (which I haven't ever tried) my Vega-Lite spec and see if I write a routine to programmatically inject the interactive-legend spec sequence into the output Vega spec and use the latter. Then, every time I modify my Vega-Lite, I compile it and post-massage it.

I'm leaning slightly towards option 3, but before I jump into this rabbit hole, I thought I'd solicit any helpful advice.

Thanks in advance,
-Ken

@domoritz
Copy link
Member

domoritz commented Apr 6, 2018

You can try to manually change a spec to manually perform 3 to see how much change is needed and how you might do it automatically. I think it will be the easiest option. Please report back so we can see how much code we need to add to Vega-Lite to add this feature.

@king612
Copy link

king612 commented May 10, 2018

+1!

This would be an awesome feature to add. In keeping with Vega-Lite philosophy, maybe just a interactive: true property to behave like the Vega example you reference. Namely, legend labels/symbols behave like checkboxes to select/deselect different series of data within the plot. Shift-click allows multiselect on the legend/series. That would be really sweet. I have a complex chart with 15 - 20 different variable plotted on a timeline, much like the example at the start of this ticket. Currently having to build all the filtering by hand. I suspect using the legend to filter would be a very common use case.

Thanks,
John K.

@jakevdp
Copy link
Contributor

jakevdp commented May 11, 2018

During the Altair tutorial yesterday, I realized you could hack a clickable legend in the current release using selections 😄

{
  "config": {"view": {"width": 400, "height": 300}},
  "hconcat": [
    {
      "mark": "point",
      "encoding": {
        "color": {"type": "nominal", "field": "Origin", "legend": null},
        "x": {"type": "quantitative", "field": "Horsepower"},
        "y": {"type": "quantitative", "field": "Miles_per_Gallon"}
      },
      "transform": [{"filter": {"selection": "legend"}}]
    },
    {
      "mark": "point",
      "encoding": {
        "color": {
          "condition": {
            "type": "nominal",
            "aggregate": "min",
            "field": "Origin",
            "legend": null,
            "selection": "legend"
          },
          "value": "lightgray"
        },
        "y": {"type": "nominal", "field": "Origin", "title": null}
      },
      "selection": {
        "legend": {
          "type": "multi",
          "encodings": ["color"],
          "on": "click",
          "toggle": "event.shiftKey",
          "resolve": "global",
          "empty": "all"
        }
      }
    }
  ],
  "data": {"url": "data/cars.json"},
  "$schema": "https://vega.github.io/schema/vega-lite/v2.4.1.json"
}

export 6

@jheer
Copy link
Member

jheer commented May 11, 2018

Nice hack! It would be great to eventually have VL/Altair produce true interactive legends that Vega already supports: https://vega.github.io/vega/examples/interactive-legend/

@brylie
Copy link
Author

brylie commented May 12, 2018

Although, the VegaLite code for interactive legend seems a bit obscure with event handling. Adding interactive: true to the legend definition would ideally be sufficient.

@kanitw
Copy link
Member

kanitw commented Nov 28, 2018

https://tech.shutterstock.com/rickshaw/ is another library that has some support for this.

@kanitw kanitw changed the title Interacting with Legends Interacting with Legends & Axes Mar 11, 2019
@kanitw
Copy link
Member

kanitw commented Mar 11, 2019

@arvind - if interacting with legend is simply applying encoding projection to the channels with legend, I'm wondering how our grammar would extend to interacting with axes.

Simplying having a projection on x and y won't convey that users want the interaction to apply to axis, not the plot (like the axis brush in this Vega example).

@arvind
Copy link
Member

arvind commented Sep 6, 2019

I see a couple of different ways of enabling interactive legends in Vega-Lite:

  1. Legends correspond to their own separate/special selections that can then be used, like a regular selection, in the remainder of the specification.

    This approach would mimic @jakevdp's example above and has the following advantages:

    • We maintain compatibility with our selections/interaction grammar: legends are not interactive by default, and users must explicitly specify the result of a legend interaction.

    • Interaction semantics are clear – legend interaction is separate from direct manipulation or input widgets which prevents any confusion (as we saw when direct manipulation co-existed alongside input widgets, feat!: by default, disable direct manipulation for widget-bound selections. #5277)

    • Users do not need to worry about understanding selection semantics (i.e., "what type of selection do I need?") and can just directly invoke the selection to drive conditional encoding or interactive filters.

    But, this approach has some open questions and expressivity bounds:

    • Does each legend correspond to its own selection or is there one selection that maps across all legends? The former is the equivalent of the following explicit specification "colorLegend": {"type": "multi", "encodings": ["color"]} while the latter would be "legend": {"type": "multi", "encodings": ["color", "shape", ...]}. Of course, we could do both.

    • If it were a legend selection, is it only populated when at least one entry is selected across all legends? Or, do we partially populate the selection if at least one legend item is selected? In this latter case, the interactive semantics would be subtly different from what they currently are for selections: currently, we populate a complete selection definition (i.e., all fields) on every interaction event. In the partial population case, we would instead build up a selection step-by-step and calculate the cross product of all selected legend items. In practice, I've not found this to be an issue but felt it worth flagging as it is a subtle nuance in semantics.

    • To drive the same interaction the legends support but via direct manipulation, users would need to explicitly add a new/custom selection and then union its invocation with their existing spec. This is more friction than opting back-in for direct manipulation when a selection is bound to input widgets. Thus, this type of accretive selection would be more difficult.

    • Presumably, a legend could be marked as "interactive": false to disable interaction. If only a legend selection was surfaced, this would be fine; if legend-specific selections were exposed, disabling legend interactivity would potentially require additional changes to the specification (or we could say that its corresponding selection always returns true).

  2. Legends are made interactive when the user defines a single or multi projected over its domain. Clicking a legend populates any selections it matches with in a partial fashion as described above. A working implementation of this approach can be found in feat: interactive legends #5305.

    Advantages include:

    • We maintain compatibility with our selections/interaction grammar: legends are not interactive by default, and users must explicitly specify the result of a legend interaction.

    • Interactive legends are a property of defined selections, in an analogous fashion to input widgets, rather than something separate. It would feel like users get interactive legends "for free" if they were already engaging in specifying a selection. A legends: false would be a selection property, to prevent a selection from being populated by legend interaction. If all selections are marked legends: false, then the legends are no longer interactive (we could imagine surfacing an interactive: false property on legends to disable interactivity across all selections).

    • We get direct manipulation + legend interaction populating the same selections. Besides starting a selection via the legend, and continuing it via direct manipulation (i.e., click a legend, shift-click points) we also gain a higher expressive ceiling (e.g., drill down interactions where a legend partially populates only one field of a selection, and then the user uses direct manipulation to complete it).

    The main disadvantage is that direct manipulation + legend interaction could cause the same confusion folks experienced when direct manipulation was left enabled alongside input widgets. Currently, feat: interactive legends #5305 treats legends as a separate "view" and applies the selection resolution scheme against it. This approach can yield unintuitive behaviors like the one shown in the following GIF:

    ezgif-4-d42cb5690891

    Here, we have a "type": "multi", "fields": ["Origin"] selection and all clicks you see are shift-clicks. A potentially confusing situation arises when I click the legend to select "USA" and then shift-click a red highlighted point expecting to toggle it out of the selection. But, this actually adds another entry to the selection state as the visualization and legends are separate views. This behavior is consistent with what happens when shift-clicking in multi-view visualizations (e.g., a SPLOM) but it's unclear whether users will think of a legend as a "separate" view.

I've been thinking about interactive legends long enough that it's no longer clear which set of tradeoff are more desirable, so I'd love folks' thoughts!

@jheer
Copy link
Member

jheer commented Sep 11, 2019

Thanks for working through this!

My preference is to define interactive legend support in terms of our existing selection abstraction. Once suitably defined, we could later consider syntactic sugar / shortcuts. Alternatively, higher level APIs like Altair could define their own shortcuts, like the existing interactive() method for charts.

I would expect legend selections to be similar to bound input widgets: a specific opt-in condition. So, I would not expect a legend to automatically become interactive if a selection was defined over it's domain; I would instead expect to specify a selection parameter indicating that legend "binding" was desired. Perhaps we could even reuse the existing bind keyword, e.g., "bind": {"input": "legend"}?

I think selections that map across multiple legends are complicated to reason about given the partial population semantics. Might it be simpler to only allow selections for individual legends, and require users to use boolean operators to combine multiple selections if desired? In this case, it would be a schema violation to define a legend-bound selection over multiple fields / encodings that are not contained within a single legend.

As previously discussed for bound widgets, I would also expect interactive legend selections to not include direct manipulation events on the chart itself by default. I would also be fine with an initial implementation that does not support the combination of interactive legends and direct manipulation in the chart (unless achieved through boolean combinations of multiple, separately defined selections).

Obviously there is some overhead for users to learn that, for example, they need to use single or multi selections for symbol legends and interval selections for quantitative legends. (Note that I don't see the latter included in current proposals, but we should at least track it for the future?) I'm not too concerned about selection type overhead, but perhaps there is an allowable shortcut here... Could one omit the selection type if bound to a legend, and infer it from the encoding information? Doesn't seem a high priority to me, though.

@jheer
Copy link
Member

jheer commented Sep 13, 2019

On the topic of interactive color gradient legends, I mocked up a Vega spec that includes a legend-bound brush. Rather than "inject" a mark into a legend group, it simply layers a brush mark on top, while getting the necessary geometry information as part of event handling.

There are two problems remaining, though:

  1. The brush position will not respond to resizing / relayout events. It will snap into proper place upon subsequent interactions though.
  2. There is not a good mechanism for scale inversion. The color scale is defined in terms of colors, but the brush operates in pixel space. In the example above I recover fractional indices along the pixel domain. While these are easy to map to true domain values for linear scales, things get much more complicated for non-linear scales. The interpolated scales in D3 do not expose a method for mapping from interpolation fractions back to domain values. So, unless I'm overlooking something, we will either need to extend those scale implementations or introduce additional utilities (not unlike our panLinear/Log/Pow/Symlog, etc. vega-util methods).

UPDATE (Sep 14): I opened a PR on d3-scale to add invert methods to interpolated (sequential, diverging) scales: d3/d3-scale#191

@zanarmstrong
Copy link

Hi all - @jakevdp pointed me to this thread based on a conversation we were having about the value of non-linear interactive color legends.

Just wanted to add my support for:

  • interactive color gradient legends, that allow the user to change the mapping from numbers to colors. Or to switch between a color gradient and a "highlight" color.
  • intentionally non-linear colormaps (which are already possible, but might be more accessible/attractive via interactive color legends).

For some context/motivation, I spoke about the relationship between color map and the question you want to ask the data (different color maps ask different questions of the data) in this talk at SciPy in 2018. At work, I've created interactive color legends for data vis-based analysis tools.

(on a related topic, I've also been finding it quite useful recently to discretize my continuous colormaps a la Adam Pearce's coloring maps blog post (@1wheel on github).

thanks all!

@jheer
Copy link
Member

jheer commented Sep 14, 2019

Thanks for the input @zanarmstrong! As discussed above, we're looking into supporting interaction with color gradient legends too, so these are useful points to keep in mind.

Discretized color maps (e.g., using quantile or quantize scales) are well-supported (here's a simple example). Obviously, interaction with discretized legends would also be great to support, and might function similarly to multi selections in symbol legends (via "clickable" color bands) and/or akin to interval selections over an ordinal domain (via a brush that selects all values within overlapping color bands).

@tonyxiao
Copy link

tonyxiao commented Apr 17, 2021

Just found this, related, though more about axis rather than legends #7394

EDIT: Turns out to be same issue, so adding example inline here


It is already possible to bind selection to a legend (https://vega.github.io/vega-lite/docs/bind.html)

However sometimes it is a lot more convenient to be interaction with axis label instead, especially when the data point represented is so small that it is very difficult to select it otherwise. Here's an illustration of the problem.

CleanShot 2021-04-17 at 10 16 07

Here's what I'm thinking of

"params": [{
    "name": "currency",
    "select": {"type": "point"},
    "bind": "axisLabel"
  }]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants