D3 Zoom for SVG Lines and SVG Paths Part Two
FREE     Duration: 7:39
Part of Course: Intermediate D3 Course
 

Takeaways:

  • The D3 Zoom Behavior, d3.behavior.zoom(), constructs a zoom behavior that creates an even listener to handle zoom gestures (panning and zooming) on the SVG elements you apply the zoom behavior onto
  • The D3 Zoom Behavior Zooming behavior is made up of being able to zoom in and out
  • The D3 Zoom Behavior Panning behavior is made up of being able to pan along the X and Y axis, like a camera panning across an image - everything stays the same distance to everything else, it's just that all of the elements move together
  • The D3 SVG Path Data Generator will take in an array of X and Y coordinates and create the SVG Path Mini-Language Instructions necessary to draw that path
  • You use D3 to set the "d" attribute of the SVG Path with the instructions created by the D# SVG Path Data Generator
  • When zooming in and out, you will want the SVG path to get bigger and smaller within the SVG Viewport
  • In terms of use the D3 SVG Path Data Generator, we see that the accessor functions use the X and Y coordinates, to create the SVG path, so you can use an X Axis Scale and Y Axis Scale to modify the X and Y coordinates before they are passed to the D3 SVG Path Data Generator
  • In terms of the specific width of the path, for zooming in and out on a D3 Generated SVG Path, you will use the d3.event.scale to multiple the stroke-width by the amount of zooming in and out that you have done

Transcript:

D3 Zoom for SVG Lines and SVG Paths Part Two

D3 Zoom for D3 Generated SVG Path


This is an SVG Path generated by D3 using the D3 Path Data Generator Function.

var originalTriangle = [{"x": 10 , "y":  10},
                        {"x": 110, "y":  10},
                        {"x": 10 , "y": 110},
                        {"x": 10 , "y":  10}];

var lineFunction = d3.svg.line()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .interpolate("linear");

var path = d3.select("body").append("svg")
  .append("path")
    .attr("d", lineFunction(originalTriangle))
    .attr("stroke", "blue")
    .attr("stroke-width", "2")
    .attr("fill", "none");

The SVG Path we are constructing is a triangle.

It consists of X and Y coordinates for the three vertices and a doubling up of the starting point.

The coordinates are passed to a D3 Path Data Generator Function that takes them in and creates the SVG Path Mini-Language Instructions.

These SVG Path Mini-Language instructions are then set as the D attribute of the SVG Path element that is appended to the SVG Element.


Given what we have covered in previous videos and in this one, let's think through how we could create a zoom behavior for this path that draws a triangle.

var path = d3.select("body").append("svg")
  .append("path")
    .attr("d", lineFunction(originalTriangle))
    .attr("stroke", "blue")
    .attr("stroke-width", "2")
    .attr("fill", "none");

When zooming in, we want the triangle to get bigger in the drawing space.

When zooming out, we want the triangle to get smaller in the drawing space.

When panning we want the triangle to move in the correct direction of the pan.


In the case of the line, when we zoom in we want the line to get longer and thicker.

// line
// zoom in  => line gets longer & line gets thicker
// zoom out => line gets shorter & line gets thinner

// triangle path
// zoom in  => triangle gets bigger & lines get thicker
// zoom out => triangle gets smaller & lines get thinner

And when we zoom out, we want the line to get shorter and thinner.

In the case of the triangle path, when we zoom in, we want the triangle to get bigger and the lines to get thicker.

And when we zoom out, we want the triangle to get smaller and the lines to get thinner.


For the line we now know that we can use the d3 dot event dot scale to scale up or down the width of the line.

// line
// zoom in/out => use d3.event.scale & updated domain xAxisScale / yAxisScale

// triangle path
// zoom in/out => use d3.event.scale & ???

We also covered how we could use the updated X and Y Axis Scales to have the change in relation to the zoom level.

For the triangle, we can use the same principle as the line to scale up or down the width of the SVG path line.

We just use the zoom multiplier to get the right behavior for the stroke-width.

However, for the SVG Path Mini-language, it is a bit harder to figure out how we can use the zoom multiplier or updated domain X and Y Axis Scales.

After all, we do not want to start messing around with the SVG Path Mini-Language and figuring out how to change it as we are zooming and panning.


If we look at the how we define and create the D3 SVG Path with the D3 Path Data Generator Function, we see that what we have to play with are the X and Y coordinates as well as the stroke, stroke-width and fill.

originalTriangle = [{"x": 10 , "y":  10},
                    {"x": 110, "y":  10},
                    {"x": 10 , "y": 110},
                    {"x": 10 , "y":  10}];

var lineFunction = d3.svg.line()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .interpolate("linear");

var path = d3.select("body").append("svg")
  .append("path")
    .attr("d", lineFunction(originalTriangle))
    .attr("stroke", "blue")
    .attr("stroke-width", "2")
    .attr("fill", "none");

The X and Y coordinates are what control the specific path segments of the triangle.

So the solution to figuring out how to make the triangle change when zooming and panning will have to involved the X and Y coordinates.


These are the SVG Path Mini-Language instructions that are generated after we run the data through the D3 Path Data Generator Function.

originalTriangle = [{"x": 10 , "y":  10},
                    {"x": 110, "y":  10},
                    {"x": 10 , "y": 110},
                    {"x": 10 , "y":  10}];
  
var lineFunction = d3.svg.line()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .interpolate("linear");
  
lineFunction(originalTriangle);
// => "M10,10L110,10L10,110L10,10"

You can see that we take in an array of points.

We define a function that changes the points to SVG Path Mini-Language Instructions.

We feed those instructions into the d attribute of the SVG path.

So we have three different places where we could modify the data to make it adhere to the zoom behavior.

The original data set, the D3 Path Data Generator and the SVG Path Mini-Language instructions.

We never want to modify the original data set and we've already covered that we don't want to get into the SVG Path Mini-Language.

So where we want to modify the code, according to the D3 zoom behavior, is the D3 Path Data Generator Function.


What this generator function does is to take in data and generate the SVG Path Mini-Language instructions for that data.

var lineFunction = d3.svg.line()
    .x(function(d) { return xAxisScale(d.x); })
    .y(function(d) { return yAxisScale(d.y); })
    .interpolate("linear");

We define accessors for the X coordinate and Y Coordinate so that it can get the data out of the array of x and y coordinate JavaScript Object Literals in the original Triangle data set.

Using the X and Y scales makes sure that the points are drawn correctly on the X and Y axis we have defined in the Inner Drawing Space.

This means that they will match up to the number line on the X axis and the number line on the Y Axis.

So when we draw the original triangle, it will use the default settings we have used for the xAxisScale and yAxisScale.


Which means that in the redraw function, we will want to do the same thing.

// Redefine the D3 SVG Line Function
var lineFunction = d3.svg.line()
    .x(function(d) { return xAxisScale(d.x); })
    .y(function(d) { return yAxisScale(d.y); })
    .interpolate("linear");

// Redraw the Triangle Path
d3.select(".triangle")
    .attr("d", lineFunction(originalTriangle))
    .attr("stroke-width", stroke_width * scaleMultiplier);

We will use the D3 Zoom Behavior redefined domain in the xAxisScale and yAxisScale.

This will ensure that the points and lines in between will match up to where the SVG Axes designate certain numbers.

Also - note that we have to make sure to multiply the stroke_width by the Zoom Scale multiplier.

In this way, we do not change the actual SVG Mini-Path Language Instructions, but the function that creates the SVG Path Mini Language Instructions.

The rest of the code we used in the previous videos and earlier example to create the SVG Axes, Scales, Viewport, Inner Drawing Space and Hidden Rectangle will all stay the same.

Next, let's take a look at how it behaves in the JavaScript Console.


This web page has the D3 library imported from the d3js.org website.


We have opened the Chrome Developer Tools and are in the Console section.


We start by defining the variables used for the Visualization.

// Variables
var svgWidth  = 400,
    svgHeight = 400,
    stroke_width = 2,
    originalTriangle = [{"x": 10 , "y":  10},
                        {"x": 110, "y":  10},
                        {"x": 10 , "y": 110},
                        {"x": 10 , "y":  10}],
    margin = {"top": 25, "right": 25, "bottom": 50, "left": 50},
    width  = svgWidth  - margin.left - margin.right,       
    height = svgHeight - margin.top  - margin.bottom;

Note that the originalTriangle variable is an array of JavaScript Object Literal Objects that define the X and Y coordinates.

Also note that we define the stroke_width here so that we can use it later in the redraw function when we are changing the stroke-width according to the zoom level.


Next, we define the SVG Viewport with a border.

// SVG Viewport
var svgViewport = d3.select("body").append("svg")
    .attr("width",  width  + margin.left + margin.right)
    .attr("height", height + margin.top  + margin.bottom)
    .style("border", "2px solid");


Next, we define the SVG Scales and Axes Functions.

// Scales
var xAxisScale = d3.scale.linear()
    .domain([0, width])
    .range([0, width]);

var yAxisScale = d3.scale.linear()
    .domain([0, height])
    .range([height, 0]);

// Axis Functions
var xAxis = d3.svg.axis()
    .scale(xAxisScale)
    .orient("bottom")
    .ticks(5);

var yAxis = d3.svg.axis()
    .scale(yAxisScale)
    .orient("left")
    .ticks(5);


Next, we define the D3 SVG Path Data Generator Function.

// D3 SVG Line Function
var lineFunction = d3.svg.line()
    .x(function(d) { return xAxisScale(d.x); })
    .y(function(d) { return yAxisScale(d.y); })
    .interpolate("linear");


Next we define the D3 Zoom Behavior Event Listener Function.

// Zoom Function Event Listener
function zoomFunction() {

    var panVector = d3.event.translate;
    var panX = panVector[0];
    var panY = panVector[1];

    var scaleMultiplier = d3.event.scale;

    d3.select("#pan_x_span").text(panX);
    d3.select("#pan_y_span").text(panY);
    d3.select("#v_scale_val").text(scaleMultiplier);

    innerSpace.select(".x.axis").call(xAxis);
    innerSpace.select(".y.axis").call(yAxis);

    // Redefine the D3 SVG Path Data Generator Function
    var lineFunction = d3.svg.line()
        .x(function(d) { return xAxisScale(d.x); })
        .y(function(d) { return yAxisScale(d.y); })
        .interpolate("linear");

    // Redraw the Triangle Path
    d3.select(".triangle")
        .attr("d", lineFunction(originalTriangle))
        .attr("stroke-width", stroke_width * scaleMultiplier);

}

The only changes here versus the previous examples are that we redefine the D3 SVG Path Data Generator Function and redraw the triangle path.

The redefining of the D3 SVG Path Data Generator Function is what makes the zoom and path behaviors both work.

We also redefine the stroke-width attribute of the triangle path by multiplying it by the scaleMultiplier so that it grows and shrinks as we zoom in and out.


Now that we have defined the event listener function, we define the D3 Zoom Behavior function.

// Zoom Behavior
var zoom = d3.behavior.zoom()
    .x(xAxisScale)
    .y(yAxisScale)
    .scaleExtent([0.2, 10])
    .on("zoom", zoomFunction);


Next we create the Inner Drawing Space and the Hidden Rectangle that allows us to use the Zoom Behavior in empty space.

// Inner Drawing Space
var innerSpace = svgViewport.append("g")
    .attr("class", "inner_space")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .call(zoom);

// Hidden Rectangle to Fully Capture Zoom Events
innerSpace.append("g").attr("class", "hidden rectangle")
    .append("rect")
    .attr("class", "background")
    .attr("x", function(d, i) { return xAxisScale(0); })
    .attr("y", function(d, i) { return yAxisScale(height); })
    .attr("width", function(d, i) { return xAxisScale(width); })
    .attr("height", function(d, i) { return yAxisScale(0); })
    .style("fill", "white");


Then we draw the X and Y Axis on the screen.

// Draw Axes
innerSpace.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

innerSpace.append("g")
    .attr("class", "y axis")
    .call(yAxis);


Then we create the Initial Drawing of the SVG Path Triangle

// Initial Drawing of the Triangle
var triangleGraph = innerSpace.append("g")
    .attr("class", "triangle_graph")
  .append("path").attr("class", "triangle")
    .attr("d", lineFunction(originalTriangle))
    .attr("stroke", "blue")
    .attr("stroke-width", stroke_width)
    .attr("fill", "none");


The last bit of code we create is the HTML Div and Span elements that will hold the key information regarding what the zoom behavior is doing as we zoom in and zoom out.

// HTML Divs and Spans To Tell Us What Is Going on
d3.select("body").append("div").attr("id", "pan_x_div").text("Pan X Translate: ");
d3.select("body").append("div").attr("id", "pan_y_div").text("Pan Y Translate: ");
d3.select("body").append("div").attr("id", "v_scale").text("D3 Zoom Scale: ");

d3.select("#pan_x_div").append("span").attr("id", "pan_x_span");
d3.select("#pan_y_div").append("span").attr("id", "pan_y_span");
d3.select("#v_scale").append("span").attr("id", "v_scale_val");


And there we go, we have now entered all of the code.


Let's explore the zooming and panning of the SVG Path Triangle

BROWSER: drag the triangle for a while

BROWSER: zoom in / out with the triangle

BROWSER: Zoom in + pan + zoom out + pan

You can see that it works with the triangle and axes for panning.

You can see it works with zooming in and out.

You can also see that as we zoom in and out the thickness of the triangle lines change.

And of course, because we are using the hidden rectangle, we can use the empty space to trigger zoom behaviors.


And with that we have covered the basics of thinking about, creating and redrawing an SVG Path that was created by the D3 SVG Path Data Generator Function with D3 Zoom behaviors attached to it.


Let's next explore how to zoom in and out as well as pan in an SVG Path that is complicated and not generated by the D3 SVG Path Data Generator Function.

<< Back To D3 Screencast and Written Tutorials Index