Event listener:
Function attached to elements used to handle specific events
The handler is called according to a specific propagation phase
event propagation phases
Phases:
↓ Capture phase: rarely used in code
→ Target phase: element that originated the event (i.e., event.target
)
↑ Bubbling phase: default used in code
Bubbling phase is the default phase
Multiple listeners can be specified
Phases are useful to respond to events when animating hierarchical data
DOM Level 2 Event Flow
Event objects are dispatched to an event target based on a propagation path
A propagation path consists of an ordered list of current event targets through which the event passes
The event object passes through one or more event phases (in order):
__Capture phase:__ The event object propagates through the target’s ancestors from the Window to the target’s parent. This phase is also known as the capturing phase.
__Target phase:__ The event object arrives at the event object’s event target. This phase is also known as the at-target phase.
__Bubble phase:__ The event object propagates through the target’s ancestors in reverse order, starting with the target’s parent and ending with the Window. This phase is also known as the bubbling phase.
Event bubbling and capturing are two ways of propagating events that occur in an element that is nested within another element, when both elements have registered a handle for that event. The event propagation mode determines the order in which elements receive the event when nesting is used.
Example using bubbling
The listener attached to the <tr> is called first
The listener attached to the <table> si called last
Example using capture
The listener attached to the <table> is called first
The listener attached to the <tr> is called last
Javascript event loop
Messages are added anytime an event occurs to the queue and there is an event listener attached to it.
Messages on the queue are handled in turn starting with the oldest one by:
Removing the message from the queue
Calling the message function passing the message as parameter
Called functions are added as a new stack frame.
Functions are processed until the stack is empty, then the event loop will process the next message in the queue.
Handling events with Javascript
DOM level 0 inline (one event handler per element)
<div id="#d0" onclick="document.getElementById('#d0').style.color = 'red'; return false;">
DOM level 0 inline (one event handler per element)
</div>
DOM level 0 traditional (one event handler per element)
var makeGreen = function () {
document.getElementById('#div01').style.color = 'green' };
document.getElementById('#div01').onclick = makeGreen; //assign an event handler
//document.getElementById('#div01').onclick = null; //remove event handler
DOM level 2 (multiple event handlers per element)
var makeBlue = function () {
document.getElementById('#d2').style.color = 'blue' };
var makeOrange = function () {
document.getElementById('#d2').style.color = 'orange' };
var div = document.getElementById('#d2');
//target.addEventListener(type, listener[, useCapture]);
div.addEventListener("click", makeBlue, true); //useCapture=true, capture phase
div.addEventListener("click", makeOrange); //useCapture=false default, use bubbling phase
//target.removeEventListener(type, listener[, useCapture]);
//div.removeEventListener("click", makeOrange, false); //remove handler
Handling events with CSS hover pseudo-class
A CSS pseudo-class is a keyword added to a selector that specifies a special state of the selected element(s).
blockquote:hover {
background-color: orangered;
color: white;
}
Handling events with D3
//selection.on(typenames[, listener[, capture]]);
//Sets DOM level 2 event listeners
//typenames: event type string
//listener: call-back
//capture: useCapture flag
var flag = false;
d3.select("#b0")
.on("click", function(event) {
console.log(event); //event object
if (event.metaKey) { //check if event has meta key
d3.select("#b1") //select and modify d01
.text('b1: b0 event')
.style("background-color", function() {
flag = !flag;
return flag ? "red" : "lightseagreen";
});
}
})
d3.select("#b1")
.on("mouseout click", function() { //multiple typenames allowed separated with spaces
d3.select(this) //this == element for this event listener
.text('b1: b1 event')
.style("background-color", function() {
flag = !flag;
return flag ? "orange" : "lightseagreen";
})
})
b0 (⌘ click)
b1
Handling events in D3 data join
var data = [15, 70, 30, 20];
var svg = d3.select('#c00');
svg.append('text')
.attr('id', 'v00')
.attr('x', '20')
.attr('y', '20')
.style('font-size', '18px');
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', function (d, i) { return i * 35; })
.attr('y', function (d) { return 80 - d; })
.attr('width', function (d) { return 30; })
.attr('height', function (d) { return d; })
.attr('fill', function (d) { return 'blue'; })
.on("mouseover", function (event, d) {
d3.select(this).style('fill', 'orangered');
console.log(event)
d3.select('#v00').text('event=' + event + ' d=' + d);
})
.on("mouseout", function (event, d) {
d3.select(this).style('fill', 'blue');
d3.select('#v00').text('');
});
Outline
Pre-attentive features
Continuity of visual queries
Interactive visualizations with D3
Events and handling events
Updating visualizations
Updating the data
Data join selections
Updating scales and axes
Animated transitions
Updating the data
array.map returns array where a function is called on every element
array.sort sorts in place the elements of the array.
array.slice shallow copy of a portion of an array into a new array object
array.shift remove the first element from the array
array.splice changes array by removing existing and/or adding new elements
d3.min compute the minimum value in an array
d3.max compute the maximum value in an array
d3.ascending comparator function to use with array.sort
d3.descending comparator function to use with array.sort
Outline
Pre-attentive features
Continuity of visual queries
Interactive visualizations with D3
Events and handling events
Updating visualizations
Updating the data
Data join selections
Updating scales and axes
Animated transitions
Data join with enter selection
//We start with some data...
var data = [{item: 'A', value: 3},
{item: 'B', value: 1},
{item: 'C', value: 2}];
enter = svg.selectAll("rect")
.data(data)
.enter();
enter.append("rect")
.attr("x", 50)
.attr("y", function(d, i) { return i * 25; })
.attr("width", function(d) { return d.value * 100; })
.attr("height", 20)
enter.append("text")
.attr('font-size', '18px')
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr("x", 20)
.attr("y", function(d, i) { return 10 + i * 25; })
.text((d) => { return d.item; });
//... and at some point we change the data
data = [{item: 'A', value: 1}, //update
{item: 'B', value: 3}, //update
//{item: 'C', value: 2}, //remove
{item: 'D', value: 3}]; //add
enter = svg.selectAll("rect") //data join
.data(data)
.enter();
enter.append("rect") //enter selection is empty!
.attr("x", 50)
.attr("y", function(d, i) { return i * 25; })
.attr("width", function(d) { return d.value * 100; })
.attr("height", 20)
enter.append("text") //enter selection is empty!
.attr('font-size', '18px')
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr("x", 20)
.attr("y", function(d, i) { return 10 + i * 25; })
.text((d) => { return d.item; });
Need data join on update and exit selections!
Data join selections
\begin{align*}
\text{Enter} &: \text{data array size} > \text{selection size} \\
\text{Update} &: \text{data array size} = \text{selection size} \\
\text{Exit} &: \text{data array size} < \text{selection size}
\end{align*}
The general update pattern
1. DATA JOIN : selection.data(...)
Join new data with old elements, if any. Returns a reference to the update selection.
2. UPDATE
Update old elements as needed.
3. ENTER: selection.enter()
Create new elements as needed.
4. ENTER + UPDATE : selection.merge(...)
Merge the entered elements with the update selection and apply operations to both.
5. EXIT: selection.exit()
Remove old elements as needed.
See Mike Bostok's General Update Pattern I , General Update Pattern II and General Update Pattern III
selection.data(dataset)
var dataset = [4, 5, 18, 23, 42];
selection group element 0 element 1 element 2 element 3 element 4 data array 4 0 5 1 18 2 23 3 42 4
//maps data according to data and selection order, the first datum
//in values is assigned to the first element in the selection and so on
d3.selectAll('div').data(dataset);
selection group div 4 div 5 div 18 div 23 div 42
Mike Bostok's How Selections Work
selection.data(dataset, key)
var dataset = [{name: "A", frequency: .08167},
{name: "B", frequency: .01492},
{name: "C", frequency: .02780},
{name: "D", frequency: .04253},
{name: "E", frequency: .12702}];
selection group element B element A element D element C element E data array {name: "A"} A {name: "B"} B {name: "C"} C {name: "D"} D {name: "E"} E
//updates can occur anywhere in the data array, depending
//on the overlap between the old and new values
function name(d) { return d.name; } //returns the key for a datum
d3.selectAll("div").data(dataset, name);
selection group div {name: "A"} div {name: "B"} div {name: "C"} div {name: "D"} div {name: "E"}
Outline
Pre-attentive features
Continuity of visual queries
Interactive visualizations with D3
Events and handling events
Updating visualizations
Updating the data
Data join selections
Updating scales and axes
Animated transitions
Updating scales and axes
Adjust the scale properties:
var dataset = [{name: "A", frequency: .08167},
{name: "B", frequency: .01492},
{name: "C", frequency: .02780},
{name: "D", frequency: .04253},
{name: "E", frequency: .12702}];
y.domain([d3.min(dataset, function (d) { return d.frequency; }),
d3.max(dataset, function (d) { return d.frequency; })])
.range([0, 600]);
x.domain(dataset.map((d) => { return d.name; })) //ordinal scale
.range([0, width]);
Redraw the axes:
svg.select('.axis')
.call(xAxis);
Outline
Pre-attentive features
Continuity of visual queries
Interactive visualizations with D3
Events and handling events
Updating visualizations
Updating the data
Data join selections
Updating scales and axes
Animated transitions
Transition between states (HTML)
Done with CSS
Transition width and background-color
<div id="delay1">Transition width and background-color</div>
<style>
#delay1 { /* initial state */
transition-property: width, background-color;
transition-duration: 3000ms;
transition-delay: 1000ms;
transition-timing-function: ease-in-out;
width: 480px;
background-color: darkorange; }
#delay1:hover { /* final state */
transition-property: width, background-color;
transition-duration: 3000ms;
transition-delay: 1000ms;
transition-timing-function: ease-in-out;
width: 960px;
background-color: cornflowerblue; }
</style>
Transition between states (SVG)
Done with CSS: same as HTML with SVG attributes!
<svg style="background-color: mistyrose" width="100%" height="30px">
<rect id="delay2" x="0" y="5" width="450" height="20"></rect>
</svg>
<style>
#delay2 {
transition-property: width, fill ;
transition-duration: 3000ms;
transition-delay: 1000ms;
transition-timing-function: ease-in-out;
width: 480px;
fill: darkorange; }
#delay2:hover {
transition-property: width, fill ;
transition-duration: 3000ms;
transition-delay: 1000ms;
transition-timing-function: ease-in-out;
width: 960px;
fill: cornflowerblue; }
</style>
Easing: CSS timing functions
Method of distorting time to control apparent motion in animation.
The timing function defines an acceleration curve controlling the speed of the transition over its duration.
/* Keyword values */
transition-timing-function: ease;
transition-timing-function: ease-in;
transition-timing-function: ease-out;
transition-timing-function: ease-in-out;
transition-timing-function: linear;
transition-timing-function: step-start;
transition-timing-function: step-end;
/* Function values */
transition-timing-function: steps(4, end);
transition-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1);
transition-timing-function: frames(10);
/* Multiple timing functions */
transition-timing-function: ease, step-start, cubic-bezier(0.1, 0.7, 1.0, 0.1);
/* Global values */
transition-timing-function: inherit;
transition-timing-function: initial;
transition-timing-function: unset;
See the Easing Functions Cheat Sheet and MDN web docs transition-timing-function
Transitions with D3
<svg id="svg20" style="background-color: mistyrose" width="100%" height="30px"></svg>
<script>
d3.select("#svg20")
.append("rect")
.attr("y", 5)
.attr("width", 480)
.attr("height", 20)
.attr("fill", "darkorange")
.on("mouseover", function () {
d3.select(this)
.transition() //selection.transition() works on the selection
.delay(1000) //transition delay in ms
.duration(3000) //transition duration in ms
.ease(d3.easeBounce) //specify easing function, defaults to d3.easeCubic
.attr("width", 960) //final transition state
.attr("fill", "cornflowerblue"); //final transition state
})
.on("mouseout", function () {
d3.select(this)
.transition()
.delay(1000)
.duration(3000)
.attr("width", 480)
.attr("fill", "darkorange");
});
//Only one transition at the time per element!
//d3.transition() works also on data joins where
//function(d, i) {...} can be used with .delay() and .duration()
//to implement staggered transitions
</script>