Animating geodemographic data with D3

What’s here

  • Some thoughts on the cost of establishing provincial bodies to design, deliver, and monitor public services whose geographic boundaries are misaligned with the national census
  • An extensive collection of geodemographic files
  • Some cool (at least I think so!) data visualizations involving sliders and animation

An aside

Since last time, I have been familiarizing myself with some of the more technical details of the Canadian government’s census and the administrative bodies that the Ontario government has established to plan, deliver, and monitor health and human services. In many instances, the geographic boundaries of these administrative bodies are difficult – if not impossible – to align with Canada’s census divisions, census subdivisions, dissemination areas, dissemination blocks, and other geographic units. This misalignment seriously challenges the use of expensive census data to address the social determinants of health and well-being in our public services. 1 The cost of this misalignment needs to figure more prominently and explicitly in any future decisions to establish new or modify existing administrative bodies. In this regard, the decision of the Ministry of Children and Youth to align the geographic boundaries of its five Integrated Service Regions (ISRs) and its thirty-three children and youth mental health Service Areas (CYMHSAs) with Ontario’s census divisions is commendable.

New geodemographic datasets

I have to admit that earlier posts have run the risk of presenting a somewhat disjointed view of the population projections and the geographic boundaries of administrative bodies that the Ontario government has established to plan, deliver, and monitor health and human services. So I want to consolidate some of my thinking and share a number of geodemographic datasets that will form the basis of future data analyses and visualizations. I have structured these datasets using three main categories – geographic unit, classification of age, and demographic variable – each with a number of sub-types:

  • Geographic Unit (4)
    • Statistics Canada
      • Census Division (CD)
    • Ontario Ministry of Children and Youth Services
      • Child and Youth Mental Health Service Area (CYMHSA)
      • Integrated Service Region (ISR)
    • Ontario Ministry of Health and Long Term Care
      • Local Health Integration Network (LHIN)
  • Classification of Age (5)
    • Life Cycle Grouping (LCG)
      • Child (0 – 14)
      • Youth (15 – 24)
    • Young Person
      • LCG-Child and LCG-combined (0 – 24)
      • CFSA-Child (0 – 17)
    • Transitional-Age Person
      • Emerging Adult (16 – 20)
  • Demographic Variable (7)
    • Population
    • Population Density
    • Population Share
    • Population Growth
      • Annual Growth
        • Change in number of persons
        • Rate of growth (%)
      • 5 Year Growth
        • Change in number of persons
        • Rate of growth (%)

We have met with most of these categories and sub-types before, or they carry their usual meanings here. One exception is the CFSA-Child (0 – 17) classification of age, which refers to the definition of “child” under the Child and Family Services Act in Ontario. CFSA-Child is central to any data analyses and visualizations relating to the CYMHSAs and ISRs defined by the province’s Ministry of Children and Youth.

We have compiled twenty-eight datasets in *.csv format that profile the different classifications of age for the years 2016 to 2041:2

Table 1. Datafiles for five classifications of age (above), 2016 to 2041 within a Geographic Unit x Demographic Variable tuple.
Geographic Unit
Demographic Variable CD CYMHSA ISR LHIN
Population data data data data
Population density data data data data
Population share data data data data
Growth
   Annual growth
       Change in number of persons data data data data
       Rate of growth (%) data data data data
   5-year growth
       Change in number of persons data data data data
       Rate of growth (%) data data data data

Sliders and animations

Of course, compiling all of these geodemographic datasets is only worthwhile if we can use them to make better decisions. In earlier posts, we have illustrated how to generate static maps of the geographic boundaries of administrative bodies like the MCYS’s CYMHSAs and ISRs, the MOHLTC’s LHINs, and we have introduced some limited capabilities for the user to interact (zoom, pan) with more dynamic maps.

Before leaving off here, I wanted to illustrate the use of sliders and animation to enhance the user’s abilities – not only to interact with maps – but to discern spatial-temporal patterns in the demographic data.

First, let’s introduce a slider that allows the user to move forward and backwards in time as we use a choropleth map to illustrate the rate of growth in the Child (0 – 14) population in Ontario’s fourty-nine Census Divisions, 2016 to 2041 [interactive version]:

Screen CDS GrowthPC Child slider
Figure 1. Choropleth with slider, illustrating annual rate of growth in Child (0 – 14) population by Census Division in Ontario, 2016 to 2041.

Second, let’s use animation to display the same data [interactive version]:

Screen CDS GrowthPC Child animation
Figure 2. Choropleth with animation, illustrating annual rate of growth in Child (0 – 14) population by Census Division in Ontario, 2016 to 2041.

Next time: I will provide a (fairly) friendly interface that will allow users to select among the 140 possible combinations of Classification of Age x Demographic Variable x Geographic Unit.

 

  1. For example, consider the challenge that Ontario’s Ministry of Finance confronts when it provides population projections for Ontario’s Local Health Integration Networks (LHINs). Except for a few cases, the boundaries of the LHINs do not conform to those of Census Divisions (CDs) or Census Subdivisions (CSDs). Thus, to take advantage of Statistics Canada’s annual updates of population projections to revise the basic demographic profiles of the LHINs, the Ministry of Finance must resort to a number of fiddles, depending on how a particular LHIN splits one or more CDs and/or CSDs. Case 1: If the LHIN consists of intact CDs (the Erie St. Clair LHIN), the Ministry of Finance’s CD-level projections for each CD are aggregated. Case 2: If the LHIN does not include any part of Toronto, York, and Peel AND its boundary splits CDs but not CSDs (the Champlain, South East, North-East, and North-West LHINs), the share-of-growth method is used. Case 3:  If the LHIN boundary splits CSDs (as well as CDs) in Toronto, York, and Peel (the Central West, Mississauga Halton, Toronto Central, and Central East LHINs), the share-of-growth method is used based on the growth of Dissemination Areas. Case 4: If the LHIN boundary splits CSDs (as well as CDs) in CDs other than Toronto, York, and Peel (the South West, Waterloo-Wellington, Hamiltion Niagara Haldimand Brant, Central, and North Simcoe Muskoka LHINs), the constant-share method is used. Additional iterative prorating procedure is required to deal with the age-sex structure of CSDs and split CSDs. All of this merely to update basic population projections! Imagine the impracticality, if not impossibility, of taking advantage of Statistics Canada’s periodic updates of subtler, and potentially more valuable, census-based socioeconomic data.
  2. For a given year within these twenty-eight datasets, the data corresponding to the Child, Youth, LCG-Combined, CFSA-Child, and Emerging Adult classifications of age are identified with the prefix Child_, Youth_, Young_, CFSAC_, and Mixed_, respectively, plus a suffix for the year. For data relating to annual growth of population, the suffix refers to the later year in the comparison.

Population Projections across CYMH Service Areas, 2016-2041

Population Projections

Recently the Ministry of Finance updated its population projections for Ontario, 2016 – 2041. These population projections are organized into 4 different datasets:

  • projections for the whole province
  • projections for each census division
  • projections for each Local Health Integration Network (LHIN)
  • projections for each Ministry of Children and Youth Services’ Service Delivery Division (SDD) region

Each dataset includes population projections by age and gender.

From the population projections for each census division (CD), we have derived the population projections (summing projections for Males and Females) for each of the CYMH Service Areas, 2016 – 2041.

The result of our work is presented in one spreadsheet, that includes six worksheets:

  • Ages x Year (Base): Population projections for each Age between 0 – 24 years by Year
  • 0-18 x Year: Population projections for the total number of 0 – 18 year old children and youth by Year
  • 0-24 x Year: Population projections for the total number of 0 – 24 year old children and youth by Year
  • 0-18 x Age Group x Year: Population projections for children and youth grouped into 5 year spans (0-4, 5-9, 10-14, and 15-18 years old)
  • 0-24 x Age Group x Year: Population projections for children and youth grouped into 5 year spans (0-4, 5-9, 10-14, 15-19, and 20-24 years old)
  • 0-24 x Life Cycle x Year: Population projections for children and youth grouped into two Life Cycle spans (Child, 0 – 14 years old and Youth, 15 – 24 years old)

Population Density

We have also calculated the land area of the individual CYMH Service Areas in order to derive their respective projected population densities. We will soon provide a speadsheet with these projections as well.

For now, let’s use the worksheet containing the projections of population and population densities for 0 – 18 year olds to add some (non-geospatial) data finally to our visualization of the CYMH Service Areas. Our illustration of two mapping techniques – proportional symbol representation (for the population projections) and choropleth (for the projected population densities) – will be illustrative only.

Proportional Symbol Representation of Population Projections

We may represent the projected number of 0 – 18 year olds across all CYMH Service Areas in a given year by combining geospatial data (in the familiar TopoJSON file format) with demographic data (in .csv format):

Proportional Symbol representation of projected population of 0-18 year olds screenshot
Figure 1. Proportional symbol representation of projected population of 0 – 18 year olds across the CYMH Service Areas in 2020.

[Interactive page – try hovering the mouse over a bubble]

Choropleth Representation of Projected Population Densities

We may also represent the projected density of 0 – 18 year olds across all CYMH Service Areas in a given year by combining geospatial data (in the familiar TopoJSON file format) with demographic data (in .csv format):

Choropleth Population Density 0-18 year olds screenshot
Figure 2. Choropleth representation of projected population density of 0 – 18 year olds across the CYMH Service Areas in 2020.

[Interactive page – try hovering the mouse over a CYMH Service Area]

Next time: We’ll look at increasing user interaction with choropleths and proportional symbol representations – including animation!

Overlaying the boundaries of the Local Health Integration Networks and Children and Youth Mental Health Service Areas in Ontario

Preamble

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 a series of previous posts, we have visualized the boundaries of the MCYS Integrated Service Regions (ISRs) and the MCYS Children and Youth Mental Health (CYMH) Service Areas.

Now we want to merge the digital boundaries of the CYMH Service Areas with the MOHLTC’s Local Health Integration Networks (LHINs) in Ontario.

The boundaries of the LHINs in 2015 are provided in the ESRI ® shapefile format (HRL035b11a_e.zip) by Statistics Canada. The .zip file contains four familiar files:

  • HRL03b11a_e.dbf
  • HRL03b11a_e.prj
  • HRL03b11a_e.shp
  • HRL03b11a_e.shx

The projection information in the HRL03b11a_e.prj file indicates that the geospatial data for the LHINs uses the EPSG 3347 PCS Lambert Conformal Conic projection. 1 So we convert the original shapefile for the LHINs to the same projection (EPSG 4269) used for the CYMH Service Areas:

ogr2ogr -f 'ESRI Shapefile' -t_srs EPSG:4269 lhins.shp HRL035a11a_e_Sept2015.shp

Next we convert the new shapefile lhins.shp to a GeoJSON file and then to a TopoJSON file (see Visualizing the MCYS Integrated Service Regions Using d3.geo for the details of this process):

ogr2ogr -t_srs EPSG:4269 -f GeoJSON lhins_geo.json lhins.shp
topojson -o lhins_topo.json --properties -- lhins_geo.json

Finally, to merge the TopoJSON file for the CYMH Service Areas and the TopoJSON file for the LHINs into a single TopoJSON file, we need to install the utility geojson-merge:

npm install -g geojson-merge

and then run:

geojson-merge cymhsas_geo.json lhins_geo.json > cymhsas_lhins_geo.json

The properties of cymhsas_lhins_topo.json include:

HR_UID -> idHealth Region ID

Property Value
area_index Identifier of CYMH Service Area
area_name Name of CYMH Service Area
ENG_LABEL -> lhin_name Name of LHIN (English)
FRE_LABEL Name of LHIN (French)

We use Notepad++ to rename ENG_LABEL to lhin_name. We then promote HR_UID to the id property of the TopoJSON file.

topojson -o cymhsas_lhins_topo.json --id-property HR_UID --properties -- cymhsas_lhins_geo.json

And finally, we use Notepad++ to add the isr and color properties to the CYMH Service Areas.

Notes:

Our visualization of the MCYS and MOHLTC geospatial data includes the following features:

  • the five Integrated Service Regions and their respective CYMH Service Areas are distinguished with different hues
  • the boundary and name of a Local Health Integrated Network are displayed when the user hovers the mouse over one of the thirteen LHINs
  • the user may pan and zoom in on the visualization

Our visualization requires only these few modifications of the Javascript we developed to display the names of the CYMH Service Areas:

/* CSS */
...
.lhin_area:hover{
  stroke: #000;
  stroke-width: 1.5 px;
}

/* Javascript */
...

function draw(topo) {

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

/* Visualize the CYMH Service Areas in colour, use id index to assign transparent colour to LHINs */
service_area.enter().insert("path")
.attr("class", "lhin_area")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.style("fill", function(d,i) { return i <= 32 ? d.properties.color : 'transparent' });

/* 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*/
service_area

.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")
.html(d.properties.lhin_name);
})

.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
});

}

… yielding the following visualization (interactive version):

Overlay LHINs on CYMH Service Areas
Figure 1. Screenshot of LHIN superimposed on CYMH Service Areas.

Next time: We’ll begin to merge the geospatial data in our TopoJSON files with demographic data in the public domain.

 

  1. Using Prj2EPSG.

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

Preamble

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">
<style>

/* CSS goes here */

/* define the container element for our map */
#map {
 margin:5% 5%;
 border:2px solid #000;
 border-radius: 5px;
 height:100%;
 overflow:hidden;
 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; 
}

</style>
<body>

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

/* load Javascript libraries for D3 and TopoJSON */
<script src="//d3js.org/d3.v3.min.js"></script>
<script src= "//d3js.org/topojson.v1.min.js"></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 */

setup(width,height);

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 = d3.select('#map').append('svg')
 .style('height', height + 'px')
 .style('width', width + 'px')
.append('g') 
.call(zoom);

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

} // end setup()

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

var projection = d3.geo.albers()
 .scale(1)
 .translate([0,0]);

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

var path = d3.geo.path()
 .projection(projection);

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

d3.json("http://cartoserve.com/maps/ontario/cymhsas33_data/cymhsas_topo.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 */
projection
 .scale(s)
 .translate(t);

/* 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 */
 
draw(topo);

}); // end function d3.json

function draw(topo) {

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

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

/* 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*/
service_area
// 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")
.html(d.properties.area_name);
}) // 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])
 );

 zoom.translate(t);
 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));
 console.log(latlon);
}

</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.

Consolidating and enhancing the visualization of CYMH Service Areas in Ontario

Preamble

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 provided the following:

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

In this post, we describe how to enhance the functionality of our visualization of the Integrated Service Regions and CYMH Service Areas and Integrated across the entire province of Ontario.

Consolidated Geodata Files for CYMH Service Areas in Ontario

In a previous post, we provided a set of GeoJSON and TopoJSON files for the individual CYMH Service Areas and their groupings into the five distinct ISRs.

For present purposes, we have created two geodata files – in GeoJSON and TopoJSON formats – for the entire set of CYMH Service Areas in Ontario.

Field Property of the CYMH Service Area
area_name Name
id ID
isr Integrated Service Region
color Color

Simple Maps of the CYMH Service Areas

By way of a quick review, let’s start with a simple map outlining the CYMH Service Areas in Ontario:

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

/* CSS goes here */

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

</style>
<body>

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

/* load Javascript libraries for D3 and TopoJSON */
<script src="//d3js.org/d3.v3.min.js"></script>
<script src= "//d3js.org/topojson.v1.min.js"></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 */

setup(width,height);

function setup(width,height){

/* 2. Create an empty root SVG element */

svg = d3.select('#map').append('svg')
 .style('height', height + 'px')
 .style('width', width + 'px')
 .append('g');

g = svg.append('g');

} // end setup()

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

var projection = d3.geo.albers()
 .scale(1)
 .translate([0,0]);

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

var path = d3.geo.path()
 .projection(projection);

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

d3.json("http://cartoserve.com/maps/ontario/cymhsas33_data/cymhsas_topo.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 */
projection
 .scale(s)
 .translate(t);

/* 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 */
 
draw(topo);

}); // end function d3.json


function draw(topo) {

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

 service_area.enter().insert("path") 
 .attr("class", "service_area")
 .attr("d", path);

} // end function draw()

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

Giving us the following visualization of 33 Service Areas [actual web page]:

CYMH Service Areas 33
Figure 1. Basic outline of 33 CYMH Service Areas in Ontario.

Note that the Javascript for creating our map includes three functions:

  • d3.json() – a built-in function to load geo data from a TopoJSON file
  • setup() – our function to create an empty root SVG element
  • draw() – our function to render the geo data

By adding just two lines of code to our draw() function, we can colour the CYMH Service Areas:


...
function draw(topo) {
 var service_area = g.selectAll(".area_name").data(topo);
 service_area.enter().insert("path") 
 .attr("class", "service_area")
 .attr("d", path)
 .attr("id", function(d,i) { return d.id; })
 .style("fill", function(d, i) { return d.properties.color; });

...

Giving us this figure [actual web page]:

CYMH Service Areas 33 colour
Figure 2. Thirty-three CYMH Service Areas in Ontario.

Adding Tooltips

Figures 1 and 2 suffer from one obvious shortcoming: none of the CYMH Service Areas is labelled. Unfortunately, our approach to labeling the CYMH Service Areas within a single Integrated Service Region breaks down when we have to contend with the scale of Ontario taken as a whole:

cymhsas02.html w labels screenshot
Figure 3. CYMH Service Areas cluttered with fixed labels.

Our best alternative is to use a tooltips to display the name of a CYMH Service Area when the user’s mouse hovers over the corresponding area of the visualization. Adding this functionality requires two sorts of modification of our Javascript.

First, we must style the Tooltips, including:

  • styling the <div> container element corresponding to the text box within which the name of the CYMH Service Area will be displayed
  • styling the text that is displayed when the user’s mouse is hovering over a service area
  • styling the tooltip so that it is hidden when the user’s mouse is not hovering over any service area

… like so:

<style>

...

/* 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; 
}

</style>

Second, we must modify our draw() function to display/hide tooltips in response to the user’s mousemove and mouseout behaviours:


<script>

var tooltip = d3.select("#map").append("div").attr("class", "tooltip hidden");

...

function draw(topo) {

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

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

/* 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*/
service_area
//mousemove behaviour
.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")
.html(d.properties.area_name);
}) // end mousemove
// mouseout behaviour
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
}); // end mouseout

} // end draw()

Giving us this sort of visualization [interactive web page]:

CYMH Service Areas 33 tooltipsr
Figure 4. Thirty-three Colour CYMH Service Areas in Ontario with Tooltips.

Next time: We’ll enhance the functionality of our visualization to allow the user to pan and zoom our map of the CYMH Service Areas in Ontario.

Visualizing the MCYS Service Areas within Integrated Service Regions Using D3.geo

Preamble

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 a previous posting, we described our method for partitioning the Shapefile archive (cymh_shapefile.zip cymh_service_areas_after_march_9_2015.zip) of the thirty-four thirty-three MCYS Children and Youth Mental Health Service Areas (CYMHSAs) into five groupings, corresponding to the MCYS Integrated Service Regions (ISRs). We also provided the GeoJSON and TopoJSON files for the individual CYMH Service Areas and their groupings into Integrated Service Regions.

Now we’ll illustrate how to use the TopoJSON files and d3.geo to visualize the CYMH Service Areas within their respective Integrated Service Regions.

In the template below,  we’ve highlighted in red any values that relate to the Integrated Service Region of interest and we’ve highlighted in blue any values that relate to the CYMH Service Areas within the Integrated Service Region:

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

/* CSS goes here */

/* fill the Service Areas using colour scheme for their corresponding Integrated Service Region*/
.<ISR>_geo.<ServiceArea-1> { fill: <colour-1>; }
.<ISR>_geo.<ServiceArea-2> { fill: <colour-2>; }
...
.<ISR>_geo.<ServiceArea-n> { fill: <colour-n>; }

/* style the Service Area boundaries */
.sa_boundary {
  fill: none;
  stroke: #000;
  stroke-width: 1.5px;
  stroke-linejoin: round;
}

/* style the Service Area labels */

.area-label {
 fill: #000;
 fill-opacity: .9;
 font-size: 10px;
 text-anchor: middle;
}

</style>
<body>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src= "//d3js.org/topojson.v1.min.js"></script>

<script>

/* 1. Set the width and height (in pixels) of the canvas */
var width = 960,
    height = 500;
 
/* 2. Create an empty root SVG element */

var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height);

/* 3. Define the Unit projection to project 3D spherical coordinates onto the 2D Cartesian plane.Note - we use the Albers equal-area conic projection. */
var projection = d3.geo.albers()
    .scale(1)
    .translate([0, 0]);

/* 4. Define the path generator - to format the projected 2D geometry for SVG */
var path = d3.geo.path()
    .projection(projection);

/* 5.0 Open the d3.json callback, and
/* 5.1 Load the TopoJSON data file. */

d3.json("<ISR>_topo.json", function(error, <ISR>_topo) {
if (error) return console.error(error);

/* 5.2 Convert the TopoJSON data back to GeoJSON format */

  var areas_var = topojson.feature(<ISR>_topo, <ISR>_topo.objects.<ISR>_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 */
projection
   .scale(s)
   .translate(t);

/* 5.3 Bind the GeoJSON data to the path element and use selection.attr to set the "d" attribute to the path data */

svg.append("path")
.datum(areas_var)
.attr("d", path);

/* 6. Draw the boundaries of the Service Areas */
  svg.append("path")
      .datum(topojson.mesh(<ISR>_topo, <ISR>_topo.objects.<ISR>_geo, function(a, b) { return a !== b; }))
      .attr("class", "sa_boundary")
      .attr("d", path);

/* 7 Colour the Service Areas */

  svg.selectAll(".<ISR>_geo")
      .data(topojson.feature(<ISR>_topo, <ISR>_topo.objects.<ISR>_geo).features)
      .enter().append("path")
      .attr("class", function(d) { return "<ISR>_geo " + d.id; })
      .attr("d", path);

/* 8 Label the Service Areas */

 svg.selectAll(".area-label")
 .data(topojson.feature(<ISR>_topo, <ISR>_topo.objects.<ISR>_geo).features)
 .enter().append("text")
 .attr("class", function(d) { return "area-label " + d.id; })
 .attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
 .attr("dy", ".35em")
 .text(function(d) { return d.properties.area_name; });

/* 9. Close the d3.json callback */

});

</script>

The following table presents the five MCYS Integrated Service Regions and their respective CYMH Service Areas. Drawing upon the resources of ColorBrewer, we’ve assigned a different hue to every Integrated Service Region and a distinctive colour of that hue to every member CYMH Service Area:

Central Region
Service Area Colour
Dufferin/Wellington #fcbba1
Halton #fc9272
Peel #fb6a4a
Simcoe #ef3b2c
Waterloo #fee0d2
York #a50f15
East Region
Service Area Colour
Durham #efedf5
Frontenac/Lennox and Addington #9e9ac8
Haliburton/Kawartha Lakes/Peterborough #dadaeb
Hastings/Prince Edward/Northumberland #bcbddc
Lanark/Leeds and Grenville #807dba
Ottawa #6a51a3
Prescott and Russell #54278f
Renfrew #54278f
Stormont, Dundas and Glengarry #3f007d
North Region
Service Area Colour
Algoma #74c476
Greater Sudbury/Manitoulin/Sudbury #a1d99b
James Bay Coast #238b45
Kenora/Rainy River #00441b
Nipissing/Parry Sound/Muskoka #c7e9c0
Thunder Bay #006d2c
Timiskaming/Cochrane

Cochrane/Timiskaming (including James Bay Coast)

#41ab5d
Toronto Region
Service Area Colour
Toronto #f16913
West Region
Service Area Colour
Brant #08519c
Chatham-Kent #c6dbef
Elgin/Oxford #4292c6
Essex #deebf7
Grey/Bruce #08519c
Haldimand-Norfolk #2171b5
Hamilton #08306b
Huron/Perth #2171b5
Lambton #9ecae1
Middlesex #6baed6
Niagara #4292c6

So, now let’s display the Integrated Service Regions and their member CYMH Service Areas:

Central Region (actual rendering):

MCYS Central Region and Member CYMH Service Areas
Figure 1. MCYS Central Region and Member CYMH Service Areas.

East Region (actual rendering):

MCYS Central Region and Member CYMH Service Areas
Figure 2. MCYS Central Region and Member CYMH Service Areas.

Toronto Region (actual rendering):

MCYS Toronto Region
Figure 3. MCYS Toronto Region.

North Region – 7 Service Areas (actual rendering):

MCYS North Region and Member CYMH Service Areas
Figure 4a. MCYS North Region and 7 Member CYMH Service Areas.

North Region – 6 Service Areas (actual rendering):

CYMH Service Areas in North Region - 33 - screenshot
Figure 4b. MCYS North Region and 6 Member CYMH Service Areas.

West Region (actual rendering):

MCYS West Region and Member CYMH Service Areas
Figure 5. MCYS West Region and Member CYMH Service Areas.

Next time: We’ll add some functionality so that users can interact with our maps.