[1,1010,1020,5000]
Four SVG circles (and an axis)
var dataPoints = [1,1010,1020,5000];
var circles = gMain.selectAll('circle')
.data(dataPoints)
.enter()
.append('circle')
.attr('r', 11)
Somewhere between 100 and 500 pixels within gMain, proportional to their value.
// create a scale for the data points
var xScale = d3.scale.linear()
.domain([0,5000])
.range([100,500])
gMain.selectAll('circle').attr('cx', function(d) { return xScale(d); });
var xScale = d3.scale.linear()
.domain([0,5000])
.range([100,500])
Map a domain interval to a range interval
So a point with value 0 has a position... 100
And a point with value 2500 has a position... 300
And a point with value 5000 has a position... 500
Difficult to distinguish the points with values 1010 and 1020.
Where are they plotted?
xScale(1010) = 180.8 xScale(1020) = 181.6
Less than a pixel apart
Use D3's zoom behavior to interactively enlarge a portion of the plot. The zoom behavior converts mouse events to two values:
Zoom in by magnifying the plotting area...
var gZoom = gMain.append('g')
var zoom = d3.behavior.zoom()
.on('zoom', function() {
gZoom.attr('transform', 'translate(' + zoom.translate()[0] + ',0)
scale(' + zoom.scale() + ')')
});
gMain.call(zoom);
gZoom.attr('transform', 'translate(' + zoom.translate()[0] + ',0)
scale(' + zoom.scale() + ')')
If we assume a zoom scale of 3 and a translation of -100:
-100 + 3 * xScale(1010) = 442.4 -100 + 3 * xScale(1020) = 444.8
This is semantic zooming
Geometric Zoom
function zoomed() {
gZoom.attr('transform',
'translate(' + zoom.translate()[0] + ',0)
scale(' + zoom.scale() + ')')
}
Semantic Zoom
function zoomed() {
circles.attr('cx',
function(d) { return zoom.translate()[0]
+ zoom.scale() * xScale(d); });
}
Let the zoom behavior change the linear scale:
var zoom = d3.behavior.zoom()
.x(xScale)
.on('zoom', zoomed);
function zoomed() {
gAxis.call(xAxis);
circles.attr('cx', function(d) { return xScale(d); });
}
As compared to:
function zoomed() {
circles.attr('cx',
function(d) { return zoom.translate()[0]
+ zoom.scale() * xScale(d); });
}
xScale = d3.scale.linear().domain([0,5000]).range([100,500])
var zoom = d3.behavior.zoom()
.x(xScale)
xScale(1010) // 180.8
zoom.scale(3).translate([-100,0]);
xScale(1010) // 442.4
Initially, We used a linear scale to convert the point's positions (from 0 to 5000) to positions in our viewport (from 100 to 500 pixels)
Then we attached this scale to the zoom behavior so it would automatically be modified when zooming.
var zoom = d3.behavior.zoom()
.x(xScale)
.on('zoom', zoomed);
Zooming can change the domain of an associated scale:
var zoom = d3.behavior.zoom()
.x(xScale)
.on('zoom', zoomed);
Zooming increases the size of the viewport relative to the data
The translate and scale are still there but they're used behind the scenes to adjust the xScale's domain.
If we want to zoom to the domain [0,2000], which translate and scale do we need?
Remember that the original xScale had a domain of [0,5000] and a range of [100,500].
If we want to zoom to the domain [0,2000], which translate and scale do we need?
Let's first calculate the zoom scale:
new_zoom_scale = old_domain_width / new_domain_width;
2.5 = 5000 / 2000;
What about the translation?
If we want to zoom to the domain [0,2000], which translate and scale do we need? We need a scale of 2.5.
What translation do we need?
point_out = translation + scale * xOrigScale(point_in);
100 = translation + 2.5 * xOrigScale(0); 100 = translation + 2.5 * 100; 100 = translation + 250; -150 = translation
Is this correct?
If we want to zoom to the domain [0,2000], which translate and scale do we need? We need a scale of 2.5 and a translate of -150.
If we want to constrain the visible area to the domain [0,2000], how do we do it?
Remember that the original xScale had a domain of [0,5000] and a range of [100,500].
Limit the translate and scale within the 'zoomed' function.
var constrainedDomain = [0,2000]
function zoomed() {
var min_scale = (origXScale.domain()[1] - origXScale.domain()[0]) /
(constrainedDomain[1] - constrainedDomain[0]);
if (zoom.scale() < min_scale)
zoom.scale(min_scale);
var max_translate = xScale.range()[0] - zoom.scale() * origXScale(constrainedDomain[0]) ;
if (zoom.translate()[0] > max_translate )
zoom.translate([max_translate, 0]);
var min_translate = xScale.range()[1] - zoom.scale() * origXScale(constrainedDomain[1]);
if (zoom.translate()[0] < min_translate )
zoom.translate([min_translate, 0]);
Use D3 v4
zoom.translateExtent([[0,0],[2000,0]])
D3 v4 introduces a number of changes but the general idea is the same:
x_new = translate_x + scale * x_old