|
Interaction
Handling input to allow data manipulation and exploration.
InteractionDescription forthcoming. Note: event handlers are inherited by children, but not (currently) from the prototype. Example: Mouseover Highlight (V1)var vis = new pv.Panel()
.def("i", -1)
.width(150)
.height(150);
vis.add(pv.Bar)
.data([1, 1.2, 1.7, 1.5, .7, .2])
.bottom(0)
.width(20)
.height(function(d) d * 80)
.left(function() this.index * 25)
.fillStyle(function() vis.i() == this.index ? "orange" : "steelblue")
.event("mouseover", function() vis.i(this.index))
.event("mouseout", function() vis.i(-1))
.anchor("top").add(pv.Label)
.visible(function() vis.i() >= 0)
.textStyle("white");
vis.render();
This example is discussed in more detail in the local variables documentation. Example: Mouseover Highlight (V2)new pv.Panel()
.width(150)
.height(150)
.add(pv.Panel)
.data([1, 1.2, 1.7, 1.5, .7, .2])
.left(function() this.index * 25)
.add(pv.Bar)
.bottom(0)
.width(20)
.height(function(d) d * 80)
.def("fillStyle", "steelblue")
.event("mouseover", function() this.fillStyle("orange")) // override
.event("mouseout", function() this.fillStyle(undefined)) // restore
.title(function() this.index)
.root.render(); Example: Mouseover Highlight (V3)new pv.Panel()
.width(150)
.height(150)
.add(pv.Panel)
.data([1, 1.2, 1.7, 1.5, .7, .2])
.left(function() this.index * 25)
.add(pv.Panel) // group bar and label for redraw
.def("active", false)
.add(pv.Bar)
.bottom(0)
.width(20)
.height(function(d) d * 80)
.fillStyle(function() this.parent.active() ? "orange" : "steelblue")
.event("mouseover", function() this.parent.active(true))
.event("mouseout", function() this.parent.active(false))
.anchor("top").add(pv.Label)
.visible(function() this.parent.active())
.textStyle("white")
.root.render(); Example: LinksHyperlinks can be implemented using the cursor property and "click" event. For best usability, you should also set the status on "mouseover" and "mouseout" events, as well as a title property for a tooltip. (Perhaps in the future, we'll make an href convenience method that sets all these properties as once.) For example: new pv.Panel()
.width(200)
.height(200)
.add(pv.Image)
.url("http://vis.stanford.edu/protovis/ex/stanford.png")
.cursor("pointer")
.title("Go to stanford.edu")
.event("mouseover", function() self.status = "Go to \"http://stanford.edu\"")
.event("mouseout", function() self.status = "")
.event("click", function() self.location = "http://stanford.edu")
.root.render(); Note that the event handler gets passed the full data stack, like other property functions. So you can compute the target URL from data if needed: .event("click", function(n) self.location
= "http://svn.prefuse.org/flare/trunk/flare/flare/src/"
+ n.keys.join("/") + ".as")See treemap.html for a live example. Design notes follow. To be reformulated into documentation soon. Notes may be out-of-date. Types of InteractionExternal controls. This is the simplest type of interaction to support, if everything happens outside of Protovis. User interface elements are created externally, and event handlers on those interface elements trigger changes to the Protovis specification, which is then re-rendered. It may be useful to support additional user interface elements that are not part of the HTML standard, such as sliders. As with time-based animation, efficient update of the visualization based on minimal changes may also be required for fluid interaction (see below). Example: periodic table of elements, with range sliders to highlight elements with the given atomic radii; crimespotting with external controls to filter by time of day or type of crime; other Schneiderman dynamic query examples. Tooltips. Another trivial form of interaction, provided that tooltips are supported natively by the rendering platform (true of SVG, VML and Flash). The tooltip text can be defined staticly. Of course, rich tooltips (arbitrary graphics, HTML) may be more difficult to implement. Example: pie chart showing value on hover. Scrolling and zooming. While browsers can support native scrolling for large visualizations, there may be other cases where we want to make scrolling part of the visualization; for example if the data set is very large and loaded (or computed) dynamically. Furthermore, browsers do not support zooming (with the exception of the iPhone and primitive text size adjustments), so having a way to zoom to a particular region is needed. Additionally, scrolling and zooming may be data-driven: for example, you might have a map of crimes, and want to resize and re-center the display to show all crimes that fit the current query. Example: Google Finance-style focus + context, where you see the relative performance of a stock over time in detail for some fixed time period, and performance for the entire history along the bottom, allowing you to select the date range for the focus; ggobi examples. Mark-driven events. Some forms of interaction are driven by discrete marks. For example, hovering over an individual crime in crimespotting will place an aura around related crimes (crimes of the same type). In the homicide visualization, clicking on a point in a scatterplot allows query relaxation: selecting related crimes by date, then week, then month. In the Job Voyager, clicking on an individual area brings that occupation to focus, hiding other occupations. As with sliders for external controls, Protovis may also benefit from integrated user interface widgets, such as pop-up menus, or shift-selection. Linking? Example: Job Voyager, clicking on an individual area. Coordinate-driven events. Other forms of interaction are driven by coordinate spaces. For example, in the homicide visualization, dragging a box across the scatterplot determines a dynamic query (selected range in two dimensions that is used to filter or highlight visual elements). This will require that we compute the local coordinates of events relative to the parent panel of the associated mark. Note that Protovis doesn't use traditional Cartesian coordinates, but instead margins, so we'll compute left, right, top and bottom for mouse events. Event handlers can then translate this back into abstract coordinates using a scale. Brushing? Example: Job Voyager, tooltip varies along x-axis by year (although technically, this may be better considered a mark-driven event, and the implementation of area and line updated accordingly such that title is not a fixed property); homicide visualization, selecting a date or age range on the scatterplot filters points on a map; some sort of linked scatterplot and pie chart, where selecting a range in two dimensions with the scatterplot shows a rollup in the pie chart by a third dimension (encoded with color). DesignMany of these interactive features are dependent on efficient rendering: efficient re-evaluation of properties, and efficent re-application of evaluated properties to the display (scene). We can treat these as partially-separable problems. Efficient Re-evaluationBecause properties can be specified as functions rather than constants, we can't know a priori which functions need to be re-evaluated—unless we want to get into parsing the code itself and looking for data dependencies, which is hard. So how will Protovis know which properties need recomputing? The user might designate some properties as "volatile" so that Protovis knows to recompute them even if their definitions haven't changed. (Constants can never be volatile.) Or, we could assume functions are volatile, and have a way of marking functions as "cached" so they aren't recomputed. Or, we could force users to dirty properties explicitly even if they haven't changed, so that Protovis knows to recompute them. Different transitions may make different properties dirty, so this may be a more precise solution than declaring properties to be volatile. Or, we could assume functions are only dependent on data, and recompute functions if the associated data has changed. Though, in many cases the data itself is computed as a function (e.g., panel recursion) so this could trigger unnecessary recomputations if only a small part of the data changes. A related optimization is tracking with properties are functions and which are constants, such that the overhead of function invocation can be avoided for constant properties. This has already been implemented. Efficient Re-displayGiven a new scene graph, the next difficulty is efficiently updating the display to match the new scene graph. First, we must identify the parts of the scene graph that have changed and need to be updated. If we mark parts of the scene graph as dirty, or issue update requests on parts of the scene graph (rather than the entire thing), we can be more efficient about updating. The level of specificity might be a mark (all of its instances and children), a mark instance, or individual properties on a given mark instance. However, incremental updates of the scene graph are tricky if the changes to the scene graph are structrual: i.e., if new SVG elements need to be created, removed, or re-ordered. New SVG elements may even be needed with non-structural changes to the scene graph, as in the case of a new title attribute (requiring an a element) or fill on a panel (requiring a rect element). A further difficulty is inserting SVG elements into the DOM at the correct location. Determining this location requires careful bookkeeping, especially since a panel may reuse the g element from the parent panel if no transform is needed. It is also not as simple as looking at the previous or next mark in the containing panel: a new mark may have been added to the panel, and may not have siblings, or those siblings may be invisible. Also consider the reverse property (and potentially a future zIndex property), which may require re-ordering SVG elements, even if the scene graph changes are not structural. Regenerating the entire SVG tree on re-render is not only inefficient, but may also interfere with event handling. For example, if a render is triggered from a mouseover event, the element that triggered the event will be removed from the DOM (potentially replaced with an identical element), and may fire a spurious mouseout event (unconfirmed). Similarly if an event handler triggers a manipulation of an SVG element, such as changing the fill color, a re-render will cause the new fill color to be discarded when the SVG tree is recreated. Structural changes to scene graphs (and the SVG tree) should be comparatively rare Event-Driven ManipulationThe ideal API for manipulating the visualization specification from an event handler is as yet unclear. The current (2.6) behavior allows evaluated properties to be reassigned from an event handler. This allows for concise proof-of-concept demonstrations of interactivity, but suffers from a number of drawbacks:
One alternative may be to allow the specification of property overrides from an event handler (e.g., mouseover). These overrides can then easily be removed from another event handlers (e.g., mouseout). |
Sign in to add a comment
Linking would indeed be really useful. Is it possible to have marks link to different pages with onclick events?
Nevermind, looks like this works as an event handler for marks to open a new url :
Yes, that works. I added a brief example of links to help illustrate.