Skip to content

Instantly share code, notes, and snippets.

@lebolo
Last active August 29, 2015 14:07
Show Gist options
  • Save lebolo/dd8d2b275ba015976ce1 to your computer and use it in GitHub Desktop.
Save lebolo/dd8d2b275ba015976ce1 to your computer and use it in GitHub Desktop.
Problems with user driven transition interruption

This is a situation where we do not want transitions to interrupt themselves. Hovering over a filter highlights various circles and clicking on it removes/adds them. To illustrate the problem, click on a filter then quickly move the mouse away.

Details

The circles have an enter transition (filter is checked - come into screen), an exit transition (filter is unchecked - leave screen), and an update transition (filter is hovered over - highlight element).

Since we're using the same control to enter/exit/highlight (i.e. checkboxes), whenever the user unchecks a filter and then moves the mouse away, the highlight transition cancels the exit transition and the circle sticks around in the state it was when it got interrupted.

Looking for a way to chain the highlight transition so that it doesn't cancel the exit transition. Can't use transition.each or transition.transition methods because the elements selected by the exit transition may not be the same as those selected by the highlight transition (e.g. user clicks on Red Filter then quickly hovers over Blue Filter).

<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*svg {
border: 1px solid black;
}*/
.shape {
fill-opacity: 0.9;
}
label {
margin-right: 10px;
}
</style>
<label id="red" class="filter" data-filter="red">
<input type="checkbox" checked />
Red Filter
</label>
<label id="blue" class="filter" data-filter="blue">
<input type="checkbox" checked />
Blue Filter
</label>
<div id="svgContainer"/>
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Setup container
var container = d3.selectAll('#svgContainer');
var width = 640,
height = 480;
var nShapes = 50;
// Create random data
var filteredData,
data = d3.range(nShapes).map(function(d, i) {
return {
id: i,
x: Math.floor(Math.random() * width),
y: Math.floor(Math.random() * height),
r: Math.floor(Math.random() * width / 15),
red: Math.floor(Math.random() * 100),
blue: Math.floor(Math.random() * 100)
};
});
// Create filter functions
var filters = {
red: function(d) {return d.red > 50;},
blue: function(d) {return d.blue > 50;}
};
// Create SVG
var svg = container.append('svg')
.attr({
width: width,
height: height
});
// Hook up hover handlers for filters
$(".filter").hover(function(e) {return onHover(e, this.dataset['filter']);});
// Hook up click handlers for filters
$(".filter").change(function(e) {
filterData();
draw();
});
// Filter data and draw the canvas
filterData();
draw();
/**
* Recalcualte filtered data
*/
function filterData() {
filteredData = data;
$('.filter').each(function(idx, el) {
var filterName = this.dataset['filter'];
var filteredOut = !$(this).find("input").prop('checked');
if (filteredOut) filteredData = filteredData.filter(function(d) {
return !filters[filterName](d);
});
});
}
/**
* Hover handler
*/
function onHover(event, filterName) {
var isHovering = (event.type == "mouseenter");
svg.selectAll('.shape')
.filter(filters[filterName])
.transition().duration(100)
.attr({
r: function (d) {return isHovering ? 1.5 * d.r : d.r;}
})
.style({
opacity: isHovering ? 0.5 : 1.0
});
}
/**
* Draw function
*/
function draw() {
var duration = 750;
var shapes = svg.selectAll('.shape').data(filteredData, function(d) {
return d.id;
});
shapes.enter().append('circle') // apply to enter selection only
.attr('class', 'shape')
.attr({
cx: 0,
cy: 0,
r: 0
})
.style('fill', 'white');
shapes.transition().duration(duration) // apply to enter + update selection
.delay(function(d) {return (d.id / nShapes) * duration;})
.attr({
cx: function(d) {return d.x;},
cy: function(d) {return d.y;},
r: function(d) {return d.r;}
})
.style({
fill: function(d) {
if (filters.red(d) && filters.blue(d)) return "purple";
if (filters.red(d)) return "red";
if (filters.blue(d)) return "blue";
return "green";
}
});
shapes.exit().transition().duration(duration) // apply to exit selection only
.delay(function(d) {return (d.id / nShapes) * duration;})
.attr({
cx: 0,
cy: 0,
r: 0
})
.style('fill', 'white')
.remove();
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment