vega / vega

A visualization grammar.
https://vega.github.io/vega
BSD 3-Clause "New" or "Revised" License
11.23k stars 1.51k forks source link

Event propagation for nested marks #700

Closed arvind closed 7 years ago

arvind commented 7 years ago

In the following specification, when clicking on a symbol mark item, neither the cell nor circle signals log a warning to the console. Instead, the cell signal is only updated if the mousedown occurs in the "transparent" space, and the circle signal only updates if I switch the event selector to use a manual filter rather than the mark name ("filter": ["event.item && event.item.mark.name === 'circle'"] rather than @circle:).

{
  "schema": {"language": "vega", "version": "3.0"},
  "padding": 10,
  "height": 100,
  "width": 100,

  "signals": [
    {
      "name": "cell", "value": null,
      "on": [
        {
          "events": "@cell:mousedown", "update": "warn('cell', group())"
        }
      ]
    }
  ],

  "data": [
    {
      "name": "iris",
      "url": "data/iris.json"
    }
  ],

  "marks": [
    {
      "name": "cell",
      "type": "group",

      "signals": [
        {
          "name": "circle",
          "on": [
            {
              "events": [{
                "source": "scope",
                "type": "@circle:click"
              }],
              "update": "warn('circle', datum, cell)"
            }
          ]
        }
      ],

      "encode": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 0},
          "width": {"value": 100},
          "height": {"value": 100},
          "fill": {"value": "transparent"},
          "stroke": {"value": "#ddd"}
        }
      },

      "scales": [
        {
          "name": "innerX", "type": "linear",
          "zero": false, "nice": true,
          "domain": {"data": "iris", "field": "petalWidth"},
          "range": "width"
        },
        {
          "name": "innerY", "type": "linear",
          "zero": false, "nice": true,
          "domain": {"data": "iris", "field": "sepalWidth"},
          "range": "height"
        }
      ],

      "marks": [
        {
          "name": "circle",
          "type": "symbol",
          "from": {"data": "iris"},
          "encode": {
            "enter": {
              "x": {
                "scale": "innerX",
                "field": "petalWidth"
              },
              "y": {
                "scale": "innerY",
                "field": "sepalWidth"
              },
              "fillOpacity": {"value": 0.5},
              "size": {"value": 36},
              "fill": {"value": "grey"}
            }
          }
        }
      ]
    }
  ]
}
jheer commented 7 years ago

Technically, there is actually no bug here.

  1. The cell should only fire an event when clicked on directly. We do not currently support "bubbling" of events up the Vega scenegraph. Whether or not we should (and what syntactic extensions this would require) is another question.
  2. For the symbol mark, you are using a selector string for the "type" property, but this is not supported. If you are using the JSON event syntax, this is assumed to be an event type string and nothing else. Since I assume you will actually be generating these event definitions from Vega-Lite, you might instead invoke the event selector parser externally, using the new parametrizable default event source. Alternatively, you can include the filter definitions directly (though certainly this is less nice).
arvind commented 7 years ago

Point (2) makes sense to me, thanks!

Regarding (1), is this an intended change from Vega 2? In the following (mostly-)equivalent spec, both the cell and circle signals populate when clicking a symbol item. Or perhaps I'm misunderstanding something? The only difference between in this spec is moving circle to the top-level, but doing so with the Vega 3 spec displays the differing behavior also.

{
  "schema": {"language": "vega", "version": "2.0"},
  "padding": 10,
  "height": 100,
  "width": 100,

  "signals": [
    {
      "name": "cell",
      "init": {},
      "streams": [
        {"type": "@cell:mousedown", "expr": "eventGroup()"}
      ]
    },
    {
      "name": "circle",
      "init": {},
      "streams": [
        {"type": "@circle:click", "expr": "eventItem()"}
      ]
    }
  ],

  "data": [
    {
      "name": "iris",
      "url": "data/iris.json"
    }
  ],

  "marks": [
    {
      "name": "cell",
      "type": "group",

      "properties": {
        "update": {
          "x": {"value": 0},
          "y": {"value": 0},
          "width": {"value": 100},
          "height": {"value": 100},
          "fill": {"value": "transparent"},
          "stroke": {"value": "#ddd"}
        }
      },

      "scales": [
        {
          "name": "innerX", "type": "linear",
          "zero": false, "nice": true,
          "domain": {"data": "iris", "field": "petalWidth"},
          "range": "width"
        },
        {
          "name": "innerY", "type": "linear",
          "zero": false, "nice": true,
          "domain": {"data": "iris", "field": "sepalWidth"},
          "range": "height"
        }
      ],

      "marks": [
        {
          "name": "circle",
          "type": "symbol",
          "from": {"data": "iris"},
          "properties": {
            "update": {
              "x": {
                "scale": "innerX",
                "field": "petalWidth"
              },
              "y": {
                "scale": "innerY",
                "field": "sepalWidth"
              },
              "fillOpacity": {"value": 0.5},
              "size": {"value": 36},
              "fill": {"value": "grey"}
            }
          }
        }
      ]
    }
  ]
}
jheer commented 7 years ago

The underlying scenegraph event handling logic should not have changed between v2 and v3. The difference stems from different "filter" implementations. In v2, we extend the event object with a list of all mark "names" along the path from the root item to the event target item, and test against that collection. In v3, we test against the name of the event target item only (see vega-parser/src/parsers/stream.js).

In any case, the current behavior is the intended behavior (on my part): the circle should be targeted by the event, not the group. The current v2 behavior is inconsistent. If one specifies @cell:mousedown an update occurs, but if one instead uses group:mousedown there is no update! The v3 semantics are (to my knowledge) consistent in how they handle event stream filters.

From a Vega-Lite generation perspective, you might simply set "interactive": false for the circles when there is no event handling directly associated with them.

Oh, and I should have mentioned earlier (in response to point 2) that Vega 3's event JSON syntax supports marktype and markname properties.