vega / vega-lite

A concise grammar of interactive graphics, built on Vega.
https://vega.github.io/vega-lite/
BSD 3-Clause "New" or "Revised" License
4.69k stars 612 forks source link

Using line's single selection with hover is quite shaky #4564

Open kanitw opened 5 years ago

kanitw commented 5 years ago

Using single selection with mouseover on the line mark is not smooth.

From this spec with "nearest": true, the GIF below shows that the selection can be quite "flashy". We can even produce a state where there is no "nearest" point selected.

flashy-hover

Fortunately, move the selection to the "point" layer makes it smooth.

allenjlee commented 5 years ago

After looking into this issue, I think the problem occurs whenever you are hovering over a region where there is no data point (i.e. a region that is in the middle of a line connecting two points). For some reason, the code automatically assumes the value of this region to be the same as the value as the very right-most data point, causing the highlighting jump since it is stored into 'highlighted_store.'

I'm not really sure what the best way to approach fixing this is. I've been looking at the compiled Vega code and the values being generated, and one heuristic I see that we may be able to use is the fact that when you hover over an existing point there is no datum field under the unit signal. However, when you hover in this middle region where no data point exists, there datum field under the unit signal is non-null. I tried to have a conditional check under the highlighted_modify signal for

"on": [{
  "update": "!unit.datum ? modify(\"highlighted_store\", highlighted_tuple, true) : null"
}]

but this doesn't seem to fix the problem fully. If we do a check for the value in highlighted_tuple such that it is not equivalent to the right most value, the problem goes away. However, this is not a good solution for the problem and it remains how to handle when the user wants to examine the right most value as well.

"on": [
    {
        "events": {"signal": "highlighted_tuple"},
        "update": "highlighted_tuple.values[0] == 1267419600000 ? null : modify(\"highlighted_store\", highlighted_tuple, true)"
    }
]

Any advice?

This is the same problem as in issue #4260

mattijn commented 5 years ago

@allenjlee Your intention is to solve the issue or you want something that works? For the latter, as kanitw mentions, move the selection to the part of the point (or rule). kapture 2019-02-25 at 21 43 40

{
  "$schema": "https://vega.github.io/schema/vega-lite/v3.json",
  "description": "Stock prices of 5 Tech Companies over Time.",
  "data": {"url": "data/stocks.csv"},
  "transform": [{"filter": {"field": "symbol", "equal": "AAPL"}}],
  "encoding": {
    "x": {"field": "date", "type": "temporal"},
    "color": {"field": "symbol", "type": "nominal"}
  },
  "layer": [
    {
      "mark": "line",
      "encoding": {"y": {"field": "price", "type": "quantitative"}}
    },
    {
      "mark": {"type": "rule", "opacity": 1},
      "selection": {
        "highlighted": {
          "type": "single",
          "on": "mouseover",
          "encodings": ["x"],
          "empty": "none",
          "nearest": true
        }
      },      
      "encoding": {
        "opacity": {
          "condition": {"selection": "highlighted", "value": 1},
          "value": 0
        }
      }
    },
    {
      "mark": {"type": "point", "opacity": 1, "size": 100, "fill": "white"},
      "encoding": {
        "y": {"field": "price", "type": "quantitative"},
        "opacity": {
          "condition": {"selection": "highlighted", "value": 1},
          "value": 0
        }
      }
    }
  ]
}
mattijn commented 5 years ago

or use a higher size value in the rule mark (eg. "size": 2)

"mark": {"type": "rule", "opacity": 1, "size": 2,"tooltip": false},

EDIT: This only works when the else value is higher than 0..

{
    "mark": {"type": "rule", "opacity": 1, "size": 2,"tooltip": false},
    "encoding": {
      "opacity": {
        "condition": {"selection": "highlighted", "value": 1},
        "value": 0.1
      } 
    }
}
kanitw commented 5 years ago

@allenjlee -- more context for you from our discussion on Slack that you may not see:

We think there is perhaps an underlying problem that leads to this issue, #4553, and #4260, namely:

The selection logic is capturing all mouseover events regardless of which mark it comes from, which seems problematic in general.

Here are some more useful comments from slack:

Hover-encoding hit testing for a line is for the whole line not individual data points (same for area). Tooltip hit testing for lines should work per-point, such that the mouse cursor must be on the line and nearby the sampled data point

FYI, if I set interactive: false on the line mark and use only the point mark then the example behaves itself. I think what is happening is that mouseover on the line is always resolving to the last datum, which is consistent with what I said earlier (it is representing the “whole line”).

I'd look into the signal flow for selections to see if we can capture more mark-specific events or check if the interaction is from the mark of interests.
Another alternative is to output interactive: true only for marks that should be interactive. That said, I think we should look into the event capturing first since that seems more like the root cause. The latter solution is more like a hack and may be limiting our interaction capability in the future.

(Btw, I assume you're working with Arvind at MIT? If so, I can add you to our team so you can assign issues that you plan to work on to you.)

allenjlee commented 5 years ago

@mattijn Oh ok thanks. I missed his last sentence at the end. It seems like this jump problem no longer exists with this solution. Also maybe I'm not understanding that the single selection is shaky, but it looks smooth to me with the example code you posted. So is this issue still open? Or is the issue trying to be solved the underlying problem you mentioned:

The selection logic is capturing all mouseover events regardless of which mark it comes from, which seems problematic in general.

@kanitw Yes, sorry for not providing context. I recently started working with Arvind and he showed me these issues ( #4553 and #4260 ) to work on. It would be great if you could add me to the team so that issues can be assigned.

Also, I was playing around and I think one potential solution may be to, as you said, add a check on the mark of interest. For example, it seems that the mark of interest is usually the mark with the name layer_0_marks (something like: item().mark.name == 'layer_0_marks'), so I added this check when updating for #4553 and the problem seemed to go away. Thanks.

kanitw commented 5 years ago

@allenjlee No problem. Just send an invitation. Welcome to the team!

kanitw commented 5 years ago

(something like: item().mark.name == 'layer_0_marks')

Yeah if that generally works, let's make VL always add that :)

arvind commented 5 years ago

Ah, I now recall why I didn't have a more precise check (e.g., for the specific mark name) in there to begin with.

If you try it out with our interactive_query_widgets example and deselect all points, a specific mark name test will cause the selection to no longer ever trigger. This is because it's defined on the lower (grey) layer and, when empty, points in the upper (colored layer) fully occlude it.

One alternative might be to test if the fields a selection is operating are over are all defined on the datum. Need to think through what happens if a particular field has an undefined (or otherwise falsy) value.

kanitw commented 5 years ago

If interactive_query_widgets is the rational behind non-precise check, I wonder if the right solution is to support setting selection at the layer level.

So we can define the selection once at the layer level, but let it propagates and applies to multiple layers. Then we can have a stricter check.

arvind commented 5 years ago

Copying from #4885:

@jheer:

Perhaps an appropriate solution is to have a policy that any marks not implicated in event processing have interactive: false set in the output Vega? One complication might be the Vega 3+ behavior of automatically enabling tooltips, in which case everything is implicated in event processing. I'm already skeptical of having tooltips always-on by default, but this seems to suggest that they might be harmful to interaction design.

@arvind:

My preference would be to use interactive: false because all the other options come with a whole set of idiosyncratic edge cases when considering multiview composition. However, interactive: false is tricky if tooltips are enabled by default. I've never been thrilled about tooltips being enabled by default so I'd be supportive of making them opt-in.