Adding pan and zoom to a visualization of the CYMH Service Areas in Ontario


This article was originally posted on May 14, 2016 and revised on July 28, 2016 to take account of changes in the geospatial representation of Children and Youth Mental Health Service Areas in Ontario. For more details, see … and then there were 33.

In previous posts, we have described:

  1. method for partitioning the Ontario government’s Shapefile archive of the thirty-four thirty-three MCYS Children and Youth Mental Health (CYMH) Service Areas into five groupings, corresponding to the MCYS Integrated Service Regions (ISRs)
  2. a method for using the TopoJSON files to visualize the CYMH Service Areas within the separate ISRs
  3. the addition of tooltips to display the names of the CYMH Service Areas across Ontario
  4. the addition of a responsive framework to ensure that our visualizations accommodate to the display capabilities of various PCs, laptops, tablets, and smart phones

Here we highlight the additional code that’s required to allow the user to pan and zoom the visualization of the CYMH Service Areas with tooltips (#3  above):

<!DOCTYPE html>
<meta charset="utf-8">

/* CSS goes here */

/* define the container element for our map */
#map {
 margin:5% 5%;
 border:2px solid #000;
 border-radius: 5px;
 background: #FFF;

/* style of the text box containing the tooltip */

div.tooltip {
 color: #222; 
 background: #fff; 
 padding: .5em; 
 text-shadow: #f5f5f5 0 1px 0;
 border-radius: 2px; 
 box-shadow: 0px 0px 2px 0px #a6a6a6; 
 opacity: 0.9; 
 position: absolute;

/* style of the text displayed in text box of the tooltip when mouse is hovering over a CYMH Service Area */

.service_area:hover{ stroke: #fff; stroke-width: 1.5px; }

.text{ font-size:10px; }

/* otherwise, the text box of the tooltip is hidden */
.hidden { 
 display: none; 


/* create the container element #map */
<div id="map"></div>

/* load Javascript libraries for D3 and TopoJSON */
<script src="//"></script>
<script src= "//"></script>

<script> // begin Javascript for visualizing the geo data

/* define some global variables */

var topo, projection, path, svg, g;

/* 1. Set the width and height (in pixels) based on the offsetWidth property of the container element #map */

var width = document.getElementById('map').offsetWidth;
var height = width / 2;

/* Call function setup() to create empty root SVG element with width, height of #map */


function setup(width,height){

/* 2. Create an empty root SVG element */
d3.behavior.zoom(), constructs a zoom behavior that creates an even listener to handle zoom gestures (mouse and touch) on the SVG elements you apply the zoom behavior onto

svg ='#map').append('svg')
 .style('height', height + 'px')
 .style('width', width + 'px')

g = svg.append('g')
    .on("click", click);

} // end setup()

/* 3. Define Unit Projection using Albers equal-area conic projection */

var projection = d3.geo.albers()

/* 4. Define the path generator - to format the projected 2D geometry for SVG */

var path = d3.geo.path()

/* 5.0 Start function d3.json() */
/* 5.1 Load the TopoJSON data file */

d3.json("", function(error, cymhsas_topo) {
if (error) return console.error(error);

/* 5.2 Convert the TopoJSON data back to GeoJSON format */
/* and render the map using Unit Projection */

var areas_var = topojson.feature(cymhsas_topo, cymhsas_topo.objects.cymhsas_geo);

/* 5.2.1 Calculate new values for scale and translate using bounding box of the service areas */
var b = path.bounds(areas_var);
var s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
var t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

/* 5.2.2 New projection, using new values for scale and translate */

/* redefine areas_var in terms of the .features array, assign array to topo */
var areas_var = topojson.feature(cymhsas_topo, cymhsas_topo.objects.cymhsas_geo).features;

topo = areas_var;

/* make the map by calling our draw() function initially within the d3.json callback function */

}); // end function d3.json

function draw(topo) {

var service_area = g.selectAll(".area_name").data(topo);

.attr("class", "service_area")
.attr("d", path)
.attr("id", function(d,i) { return; })
.style("fill", function(d, i) { return; })
.attr("title", function(d,i) { return; });

/* define offsets for displaying the tooltips */
var offsetL = document.getElementById('map').offsetLeft+20;
var offsetT = document.getElementById('map').offsetTop+10;

/* toggle display of tooltips in response to user mouse behaviours*/
// begin mousemove
.on("mousemove", function(d,i) {
var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
}) // end mousemove
// begin mouseout
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
}); // end mouseout

} // end draw()

function move() {

 var t = d3.event.translate;
 var s = d3.event.scale; 
 zscale = s;
 var h = height/4;

 t[0] = Math.min(
 (width/height) * (s - 1), 
 Math.max( width * (1 - s), t[0] )

 t[1] = Math.min(
 h * (s - 1) + h * s, 
 Math.max(height * (1 - s) - h * s, t[1])

 g.attr("transform", "translate(" + t + ")scale(" + s + ")");

 //adjust the Service Area hover stroke width based on zoom level
 d3.selectAll(".service_area").style("stroke-width", 1.5 / s);


/* our function click() uses the .invert() configuration method */
/* to project backward from Cartesian coordinates (in pixels) to spherical coordinates (in degrees) */

function click() {
 var latlon = projection.invert(d3.mouse(this));

</script> // end Javascript for visualizing the geo data

Giving us the following interactive visualization of the CYMH Service Areas in Ontario.

Next time: We will show how to merge our visualization of the geography of the CYMH Service Areas with other data about the populations and service providers within these Service Areas.