Intro
Focus + context visualizations are quite useful when we want to zoom into some part of a visualization, but are unwilling to give up the “bird’s eye” view of the entire picture. In this case, we distort the part of the visual on which we want to focus, while preserving the entire view.
In D3, this task is accomplished through “fisheye distortion”. Mike Bostock has examples of usage of his fisheye plugin on his site.
The problem is this plugin does not support bar charts, where the application could also be quite useful. I believe lack of support is explained by the fact that the ordinal scale rangeBand()
function does not take any parameters, which is fine for any bar chart: all you need is to split your range into equal regions. When applying a distortion, however, the chunk of the range devoted to a particular part of the input depends on the position of this particular chunk. So extending the plugin is really trivial, just need to provide a new signature for the rangeBand()
function.
Here is what a finished example looks like:
The complete code for it, including the plugin can be found on JSFiddle.
In order to create it, I have modified an existing example by a StackOverflow user (thank you very much!).
Usage
The actual fisheye plugin spans lines 1 – 144 in the jsfiddle cited above. Cut it, save to a file.
- Create your ordinal scale for the bar chart using the plugin:
var x = d3.fisheye.ordinal().rangeRoundBands([0, w - p[1] - p[3]]) .distortion(0.9);
-
Replace all calls to rangeBand() with calls to rangeBand(d.x):
// Add a rect for each date. var rect = cause.selectAll("rect") .data(Object) .enter().append("svg:rect") .attr("x", function(d) { return x(d.x); }) .attr("y", function(d) { return -y(d.y0) - y(d.y); }) .attr("height", function(d) { return y(d.y); }) .attr("width", function(d) {return x.rangeBand(d.x);}); // Add a label per date. var label = svg.selectAll("text") .data(x.domain()) .enter().append("svg:text") .attr("x", function(d) { return x(d) + x.rangeBand(d.x) / 2; }) .attr("y", 6) .attr("text-anchor", "middle") .attr("dy", ".71em") .text(format);
-
Add mouse interaction to your main container, to update the focus of the distortion, and redraw the affected elements:
//respond to the mouse and distort where necessary svg.on("mousemove", function() { var mouse = d3.mouse(this); //refocus the distortion x.focus(mouse[0]); //redraw the bars rect .attr("x", function(d) { return x(d.x); }) .attr("width", function(d) {return x.rangeBand(d.x);}); //redraw the text label.attr("x", function(d) { return x(d) + x.rangeBand(d.x) / 2; }); });
Et voilĂ !
Hi, Thanks for posting this. How does one increase the degree of distortion? for instance if I wanted an exaggerated amount of distortion I thought that increasing the distortion value defined in circular would do the trick, but that has zero effect.
circular: function() {
var radius = 200,
distortion = 30, // no change if this is changed
Hi There, Thanks for posting this. How does one change the amount of distortion in your example (to increase it)
I changed the distortion value under circular but this seems to have no effect.
circular: function() {
var radius = 200,
distortion = 2, //changing this does nothing.