Skip to content

Instantly share code, notes, and snippets.

@erp12
Created March 16, 2018 14:49
Show Gist options
  • Save erp12/c6642e325e2dac04643e505b9a0753ef to your computer and use it in GitHub Desktop.
Save erp12/c6642e325e2dac04643e505b9a0753ef to your computer and use it in GitHub Desktop.

Explanation of dataset

The dataset is a geojson file that specifies the shape of all the counties in the united states. I got shapefiles from the census website and converted into them into geojson using the QGIS software.

The color attribute is mapped to the length of the name of the county. The buttons at the top allow for zooming. The sliders along the left and bottom of the map allow for panning.

I struggled to combine the geojson dataset with a secondary dataset in a way that wouldn't cause the visualization to take an infeasible amount of time to draw.

Type of projection

I chose the Mercator projection because it preserves the angles and shapes of the paths. It shows lines of latitude as straight. In the context of my visualization, this causes borders like the border between the USA and Canada to appear straight.

The size of each county is distorted based on how far north it is.

// d3.tip
// Copyright (c) 2013 Justin Palmer
// ES6 / D3 v4 Adaption Copyright (c) 2016 Constantin Gavrilete
// Removal of ES6 for D3 v4 Adaption Copyright (c) 2016 David Gotz
//
// Tooltips for d3.js SVG visualizations
d3.functor = function functor(v) {
return typeof v === "function" ? v : function() {
return v;
};
};
d3.tip = function() {
var direction = d3_tip_direction,
offset = d3_tip_offset,
html = d3_tip_html,
node = initNode(),
svg = null,
point = null,
target = null
function tip(vis) {
svg = getSVGNode(vis)
point = svg.createSVGPoint()
document.body.appendChild(node)
}
// Public - show the tooltip on the screen
//
// Returns a tip
tip.show = function() {
var args = Array.prototype.slice.call(arguments)
if(args[args.length - 1] instanceof SVGElement) target = args.pop()
var content = html.apply(this, args),
poffset = offset.apply(this, args),
dir = direction.apply(this, args),
nodel = getNodeEl(),
i = directions.length,
coords,
scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
nodel.html(content)
.style('position', 'absolute')
.style('opacity', 1)
.style('pointer-events', 'all')
while(i--) nodel.classed(directions[i], false)
coords = direction_callbacks[dir].apply(this)
nodel.classed(dir, true)
.style('top', (coords.top + poffset[0]) + scrollTop + 'px')
.style('left', (coords.left + poffset[1]) + scrollLeft + 'px')
return tip
}
// Public - hide the tooltip
//
// Returns a tip
tip.hide = function() {
var nodel = getNodeEl()
nodel
.style('opacity', 0)
.style('pointer-events', 'none')
return tip
}
// Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value.
//
// n - name of the attribute
// v - value of the attribute
//
// Returns tip or attribute value
tip.attr = function(n, v) {
if (arguments.length < 2 && typeof n === 'string') {
return getNodeEl().attr(n)
} else {
var args = Array.prototype.slice.call(arguments)
d3.selection.prototype.attr.apply(getNodeEl(), args)
}
return tip
}
// Public: Proxy style calls to the d3 tip container. Sets or gets a style value.
//
// n - name of the property
// v - value of the property
//
// Returns tip or style property value
tip.style = function(n, v) {
// debugger;
if (arguments.length < 2 && typeof n === 'string') {
return getNodeEl().style(n)
} else {
var args = Array.prototype.slice.call(arguments);
if (args.length === 1) {
var styles = args[0];
Object.keys(styles).forEach(function(key) {
return d3.selection.prototype.style.apply(getNodeEl(), [key, styles[key]]);
});
}
}
return tip
}
// Public: Set or get the direction of the tooltip
//
// v - One of n(north), s(south), e(east), or w(west), nw(northwest),
// sw(southwest), ne(northeast) or se(southeast)
//
// Returns tip or direction
tip.direction = function(v) {
if (!arguments.length) return direction
direction = v == null ? v : d3.functor(v)
return tip
}
// Public: Sets or gets the offset of the tip
//
// v - Array of [x, y] offset
//
// Returns offset or
tip.offset = function(v) {
if (!arguments.length) return offset
offset = v == null ? v : d3.functor(v)
return tip
}
// Public: sets or gets the html value of the tooltip
//
// v - String value of the tip
//
// Returns html value or tip
tip.html = function(v) {
if (!arguments.length) return html
html = v == null ? v : d3.functor(v)
return tip
}
// Public: destroys the tooltip and removes it from the DOM
//
// Returns a tip
tip.destroy = function() {
if(node) {
getNodeEl().remove();
node = null;
}
return tip;
}
function d3_tip_direction() { return 'n' }
function d3_tip_offset() { return [0, 0] }
function d3_tip_html() { return ' ' }
var direction_callbacks = {
n: direction_n,
s: direction_s,
e: direction_e,
w: direction_w,
nw: direction_nw,
ne: direction_ne,
sw: direction_sw,
se: direction_se
};
var directions = Object.keys(direction_callbacks);
function direction_n() {
var bbox = getScreenBBox()
return {
top: bbox.n.y - node.offsetHeight,
left: bbox.n.x - node.offsetWidth / 2
}
}
function direction_s() {
var bbox = getScreenBBox()
return {
top: bbox.s.y,
left: bbox.s.x - node.offsetWidth / 2
}
}
function direction_e() {
var bbox = getScreenBBox()
return {
top: bbox.e.y - node.offsetHeight / 2,
left: bbox.e.x
}
}
function direction_w() {
var bbox = getScreenBBox()
return {
top: bbox.w.y - node.offsetHeight / 2,
left: bbox.w.x - node.offsetWidth
}
}
function direction_nw() {
var bbox = getScreenBBox()
return {
top: bbox.nw.y - node.offsetHeight,
left: bbox.nw.x - node.offsetWidth
}
}
function direction_ne() {
var bbox = getScreenBBox()
return {
top: bbox.ne.y - node.offsetHeight,
left: bbox.ne.x
}
}
function direction_sw() {
var bbox = getScreenBBox()
return {
top: bbox.sw.y,
left: bbox.sw.x - node.offsetWidth
}
}
function direction_se() {
var bbox = getScreenBBox()
return {
top: bbox.se.y,
left: bbox.e.x
}
}
function initNode() {
var node = d3.select(document.createElement('div'))
node
.style('position', 'absolute')
.style('top', 0)
.style('opacity', 0)
.style('pointer-events', 'none')
.style('box-sizing', 'border-box')
return node.node()
}
function getSVGNode(el) {
el = el.node()
if(el.tagName.toLowerCase() === 'svg')
return el
return el.ownerSVGElement
}
function getNodeEl() {
if(node === null) {
node = initNode();
// re-add node to DOM
document.body.appendChild(node);
};
return d3.select(node);
}
// Private - gets the screen coordinates of a shape
//
// Given a shape on the screen, will return an SVGPoint for the directions
// n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
// sw(southwest).
//
// +-+-+
// | |
// + +
// | |
// +-+-+
//
// Returns an Object {n, s, e, w, nw, sw, ne, se}
function getScreenBBox() {
var targetel = target || d3.event.target;
while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) {
targetel = targetel.parentNode;
}
var bbox = {},
matrix = targetel.getScreenCTM(),
tbbox = targetel.getBBox(),
width = tbbox.width,
height = tbbox.height,
x = tbbox.x,
y = tbbox.y
point.x = x
point.y = y
bbox.nw = point.matrixTransform(matrix)
point.x += width
bbox.ne = point.matrixTransform(matrix)
point.y += height
bbox.se = point.matrixTransform(matrix)
point.x -= width
bbox.sw = point.matrixTransform(matrix)
point.y -= height / 2
bbox.w = point.matrixTransform(matrix)
point.x += width
bbox.e = point.matrixTransform(matrix)
point.x -= width / 2
point.y -= height / 2
bbox.n = point.matrixTransform(matrix)
point.y += height
bbox.s = point.matrixTransform(matrix)
return bbox
}
return tip
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="//rawgithub.com/Caged/d3-tip/master/examples/example-styles.css">
<style>
body {
top:0;right:0;bottom:0;left:0;
margin:10;
position:fixed;
font-family: sans-serif;
}
input[type=range][orient=vertical] {
/*Credit to https://stackoverflow.com/questions/15935837/how-to-display-a-range-input-slider-vertically */
writing-mode: bt-lr; /* IE */
-webkit-appearance: slider-vertical; /* WebKit */
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1em;
}
</style>
</head>
<body>
<h1>COMPSCI590V HW3</h1>
<h2>Edward Pantridge</h2>
<span style="margin-left: 20px;">
<button onclick="resetMap()">Reset</button>
<button onclick="zoomIn()">Zoom In</button>
<button onclick="zoomOut()">Zoom Out</button>
</span>
<br>
<span>
<input id="ySlider" type="range" orient="vertical" min="-1" max="1" value="0" step="0.1"/>
<svg></svg>
</span>
<br>
<input id="xSlider" type="range" min="-1" max="1" value="0" step="0.1"/>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.5/d3-legend.min.js"></script>
<script src="d3-tip.js"></script>
<script>
// Constant values
const sliderThickness = 20;
const width = document.body.clientWidth - (2 * sliderThickness) - 50;
const height = width / 2.5;
// Prepare SVG
const svg = d3
.select('svg')
.attr('width', width)
.attr('height', height);
// Setup control elements
const xSlider = document.getElementById("xSlider");
const ySlider = document.getElementById("ySlider");
xSlider.style.height = sliderThickness + "px";
xSlider.style.width = width + "px";
xSlider.style.marginLeft = sliderThickness + "px";
ySlider.style.width = sliderThickness + "px";
ySlider.style.height = height + "px";
xSlider.onchange = function() {
redrawMap();
}
ySlider.onchange = function() {
redrawMap();
}
// State of control elements
var zoomLevel = 1;
var initialCenter = null;
// Create hover tool tip
const tool_tip = d3.tip()
.attr("class", "d3-tip")
.offset([-8, 0])
.html(d => "Name: " + d.properties.NAME);
function redrawMap() {
d3.json("us-counties.geojson", function(err, geojson) {
console.log(err);
svg.selectAll("*").remove();
// Create scales for sliders
const xOffset = d3.scaleLinear()
.domain([-1, 1])
.range([(width * zoomLevel) / 2, (-width * zoomLevel) / 2])
.clamp(true);
const yOffset = d3.scaleLinear()
.domain([-1, 1])
.range([(-height * zoomLevel) / 2, (height * zoomLevel) / 2])
.clamp(true);
const center = d3.geoCentroid(geojson)
var color = d3.scaleLinear()
.domain([1, d3.max(geojson.features, nameLength)])
.clamp(true)
.range([0, 1]);
const projection = d3.geoMercator()
.center([center[0], center[1]])
.scale(0.75 * width * zoomLevel)
.translate([width / 2, height / 2])
const path = d3.geoPath()
.projection(projection);
svg.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("d", path)
.attr("transform", `translate(${xOffset(xSlider.value)}, ${yOffset(ySlider.value)})`)
.attr("fill", "green")
.attr("opacity", d => color(nameLength(d)))
.attr("stroke", "#222")
.on('mouseover', tool_tip.show)
.on('mouseout', tool_tip.hide);
svg.call(tool_tip);
});
}
function nameLength(d) {
return d.properties.NAME.length;
}
function zoomIn() {
zoomLevel = zoomLevel + 0.12;
redrawMap();
}
function zoomOut() {
zoomLevel = zoomLevel - 0.12;
redrawMap();
}
function resetMap() {
zoomLevel = 1;
xSlider.value = 0;
ySlider.value = 0;
redrawMap();
}
// Call functions that should run when page loads
redrawMap();
</script>
</body>
</html>
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment