Skip to content

Instantly share code, notes, and snippets.

@JaimeStill
Created April 23, 2016 06:31
Show Gist options
  • Save JaimeStill/ff35ba291366c51684d1e0b7e9376614 to your computer and use it in GitHub Desktop.
Save JaimeStill/ff35ba291366c51684d1e0b7e9376614 to your computer and use it in GitHub Desktop.
D3 Maps Notes
(function () {
var createBoundingMap = function () {
var height = 600,
width = 900;
var projection = d3.geo.mercator();
var mexico = void 0;
var path = d3.geo.path().projection(projection);
var svg = d3.select('#bounding-map')
.append('svg')
.attr({
'width': width,
'height': height,
'overflow': 'hidden'
});
d3.json('geo-data.json', function (data) {
var states = topojson.feature(data, data.objects.MEX_adm1);
projection.scale(1).translate([0, 0]);
var b = path.bounds(states.features[5]);
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];
projection.scale(s).translate(t);
var map = svg.append('g')
.attr('class', 'boundary');
mexico = map.selectAll('path')
.data(states.features);
// Enter
mexico.enter()
.append('path')
.attr('d', path);
});
};
var createChoroplethMap = function () {
var height = 600,
width = 900;
var projection = d3.geo.mercator();
var mexico = void 0;
var path = d3.geo.path().projection(projection);
var svg = d3.select('#choropleth-map')
.append('svg')
.attr({
'width': width,
'height': height,
'overflow': 'hidden'
});
d3.json('geo-data.json', function (data) {
var states = topojson.feature(data, data.objects.MEX_adm1);
projection.scale(1).translate([0, 0]);
var b = path.bounds(states);
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];
projection.scale(s).translate(t);
var map = svg.append('g')
.attr('class', 'boundary');
mexico = map.selectAll('path')
.data(states.features);
// Enter
mexico.enter()
.append('path')
.attr('d', path);
// Update
var color = d3.scale.linear().domain([0, 33]).range(['red', 'yellow']);
mexico.attr({
'fill': function (d, i) { return color(i); },
'stroke': '#333',
'stroke-width': 1
});
});
};
var createClickableMap = function () {
var height = 600,
width = 900;
var projection = d3.geo.mercator();
var mexico = void 0;
var geoId = function (d) {
return "c" + d.properties.ID_1;
};
var click = function (d) {
mexico.attr('fill-opacity', 0.2);
d3.select('#' + geoId(d))
.attr('fill-opacity', 1);
}
var path = d3.geo.path().projection(projection);
var svg = d3.select('#clickable-map')
.append('svg')
.attr({
'width': width,
'height': height,
'overflow': 'hidden'
});
d3.json('geo-data.json', function (data) {
var states = topojson.feature(data, data.objects.MEX_adm1);
projection.scale(1).translate([0, 0]);
var b = path.bounds(states);
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];
projection.scale(s).translate(t);
var map = svg.append('g')
.attr('class', 'boundary');
mexico = map.selectAll('path')
.data(states.features);
// Enter
mexico.enter()
.append('path')
.attr({
'd': path,
'id': geoId
})
.on('click', click);
// Update
var color = d3.scale.linear().domain([0, 33]).range(['red', 'yellow']);
mexico.attr({
'fill': function (d, i) { return color(i); },
'stroke': '#333',
'stroke-width': 1
});
});
}
var createTransitionMap = function () {
var height = 600,
width = 900;
var projection = d3.geo.mercator();
var mexico = void 0;
var path = d3.geo.path().projection(projection);
var svg = d3.select('#transition-map')
.append('svg')
.attr({
'width': width,
'height': height,
'overflow': 'hidden'
});
d3.json('geo-data.json', function (data) {
var states = topojson.feature(data, data.objects.MEX_adm1);
projection.scale(1).translate([0, 0]);
var b = path.bounds(states);
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];
projection.scale(s).translate(t);
var map = svg.append('g')
.attr('class', 'boundary');
mexico = map.selectAll('path')
.data(states.features);
// Enter
mexico.enter()
.append('path')
.attr('d', path);
// Update
var color = d3.scale.linear().domain([0, 33]).range(['red', 'yellow']);
mexico.attr({
'fill': function (d, i) { return color(i); },
'stroke': '#333',
'stroke-width': 1
});
setInterval(function () {
mexico.transition().duration(500)
.style('fill', function (d) {
return color(Math.floor((Math.random() * 32) + 1));
});
}, 2000);
});
};
createBoundingMap();
createChoroplethMap();
createClickableMap();
createTransitionMap();
}());
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<html>
<head>
<title>Experiments</title>
<meta charset="utf-8" />
<link href="site.css" rel="stylesheet" />
</head>
<body>
<h2>Experiment 1 - Adjusting the Bounding Box</h2>
<div id="bounding-map"></div>
<p>
For this experiment, we manually zoom in to a state of Mexico using the bounding box. rather than initializing the bounds
based on all of the features in the states data, we want to initialize it based ont he sixth element of the features array
contained in the states object.
</p>
<div class="code-container">
<pre>var b = path.bounds(states.features[5]);</pre>
</div>
<p>
This basically reduces the min / max of the boundary box to include the geographic coordinates for one state in mexico, and
D3 scales and translates this information automatically based on the scale and translation algorithms used. This can be useful
in situations where you might not have the data you need in isolation from the surrounding areas.
</p>
<h2>Experiment 2 - Creating Choropleths</h2>
<div id="choropleth-map"></div>
<p>
One of the most common uses of D3.js maps is to make choropleths. This visualization gives you the ability to discern between regions,
giving them a different color. Normally, this color is associated with some other value, for instance, levels of influenza or a company's
sales.
</p>
<p>
The <span class="snippet">color</span> variable uses another valuable D3 function named <span class="snippet">scale</span>. This
<span class="snippet">color</span> function looks for any number between 0 and 33 in an input domain. D3 linearly maps these input
values to a color between red and yellow in the output range. D3 has included the capability to automatically map colors in a linear
range to a gradient. This means that executing the new function, <span class="snippet">color</span>, with 0 will return the color
red, <span class="snippet">color(15)</span> will return an orange color, and <span class="snippet">color(33)</span> will return yellow.
</p>
<p>
In the update section, we set the fill property of the path to the new color function. This will provide a linear scale of colors and use the
index value <span class="snippet">i</span> to determine what color should be returned. If the color was determined by a different value of the
datum, for instance, <span class="snippet">d.sales</span>, then you would have a choropleth where the colors actually represent sales.
</p>
<div class="code-container">
<pre>var color = d3.scale.linear()
.domain([0, 33])
.range(['red', 'yellow']);
mexico.attr('fill', function (d, i) { return color(i); });</pre>
</div>
<h2>Experiment 3 - Adding Click Events</h2>
<div id="clickable-map"></div>
<p>
We need a quick reference to each state in the country. To accomplish this, we will create a new function called <span class="snippet">geoId</span>
irght below the <span class="snippet">mexico</span> variable.
</p>
<div class="code-container">
<pre>var geoId = function (d) {
return "c" + d.properties.ID_1;
}</pre>
</div>
<p>
This function takes in a <span class="snippet">state</span> data element and generates a new selectable ID based on the <span class="snippet">ID_1</span>
property found in the data. The <span class="snippet">ID_1</span> property contains a unique numeric value for every state in the array. If we insert
this as an <span class="snippet">id</span> attribute into the DOM, then we would create a quick and easy way to select each state in the country.
</p>
<p>
Next, we need to defne a click function that makes it easy to separate what the click is doing. The <span class="snippet">click</span> method receives
the datum and changes the fill opacity value of all the states to 0.2. This is done so that when you click on one state and then on the other, the previous
state does not maintain the <em>clicked</em> state. Notice that the function call is iterating through all the elements of the DOM, using the D3 update pattern.
After making all the states transparent, we will set a fill-opacity of 1 for the given clicked item. This removes all transparent styling from the selected
state.
</p>
<div class="code-container">
<pre>var click = function (d) {
mexico.attr('fill-opacity', 0.2);
d3.select('#' + geoId(d)).attr('fill-opacity', 1);
}</pre>
</div>
<p>
All that's left is to bind the click event to the click function that we created by updating the <span class="snippet">enter</span> function:
</p>
<div class="code-container">
<pre>mexico.enter()
.append('path')
.attr({
'd': path,
'id': geoId
})
.on('click', click);</pre>
</div>
<h2>Experiment 4 - Using Updates and Transitions</h2>
<div id="transition-map"></div>
<p>
We will assign a random number between 1 and 32 to the existing <span class="snippet">color</span> function. Then, leverage a D3 method to
smoothly transition between the random color changes. Every 2 seconds, the <span class="snippet">mexico</span> update section should be
executed and the color set to a random number between 1 and 32. The new transition and duration methods transition from the previous
state to the new state over 500 milliseconds (half a second).
</p>
<div class="code-container">
<pre>setInterval(function () {
mexico.transition().duration(500)
.style('fill', function (d) {
return color(Math.floor((Math.random() * 32) + 1));
});
}, 2000);</pre>
</div>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://d3js.org/topojson.v1.js" type="text/javascript"></script>
<script src="experiments.js" type="text/javascript"></script>
</body>
</html>
body {
font-family: 'Segoe UI', sans-serif;
padding-left: 1em;
}
p, li {
max-width: 60em;
}
pre {
color: limegreen;
font-weight: bold;
font-family: Consolas, monospace;
margin: 0;
padding: 0;
display: inline-block;
}
.code-container {
padding: .5em;
background-color: #eee;
color: springgreen;
display: inline-block;
word-wrap: break-word;
}
.code-block {
display: block;
}
span.snippet{
color: limegreen;
font-weight: bold;
font-family: Consolas, monospace;
margin: 0;
padding: 0;
}
.inline-element {
display: inline-block;
margin-right: 1em;
margin-bottom: 1em;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment