Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hexagons! #31

Open
wants to merge 8 commits into
base: gh-pages
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ <h1>Petition Map <span class="built-by">built by <a href="http://unboxed.co/">Un
</div>
</details>
</div>
<div class="controls-row">
<details>
<summary class="flat_button" tabindex="8" title="Choose map style">Choose map style</summary>
<div>
<label class="inline" title="Show geographic map"><input type="radio" name="map_mode" value="topo" checked tabindex="10">Geographic</label>
<label class="inline" title="Show hexagon map"><input type="radio" name="map_mode" value="hex" tabindex="10">Hexagons</label>
</div>
</details>
</div>
</section>
<section id="map">
<div id="spinner_area">
Expand All @@ -166,6 +175,7 @@ <h1>Petition Map <span class="built-by">built by <a href="http://unboxed.co/">Un
<script src="plugins/vex/js/vex.combined.min.js"></script>
<script>vex.defaultOptions.className = 'vex-theme-plain';</script>
<script src="plugins/details/details.polyfill.js"></script>
<script src="plugins/d3-hexjson/d3-hexjson.min.js"></script>

<!--[if lt IE 9]>
<script src="plugins/html5shiv/html5shiv.js"></script>
Expand Down Expand Up @@ -201,6 +211,14 @@ <h2>I can't find a petition in the drop down list!</h2>
<a href="https://github.com/martinjc/UK-GeoJson">GeoJSON and TopoJSON UK Boundary Data</a>
by Martin Chorley is licensed under a Creative Commons Attribution 4.0 International License.
</p>
<p>
<a href="https://github.com/odileeds/hexmaps/">HexJSON UK Constituencies data</a>
by ODI Leeds is licensed under an MIT License.
</p>
<p>
<a href="https://github.com/olihawkins/d3-hexjson">D3-HexJSON</a>
by Oliver Hawkins is licensed under the BSD 3-Clause "New" or "Revised" License.
</p>
<p>
Contains Ordnance Survey, Office of National Statistics and National Records Scotland data © Crown copyright and database right [2017]. Ordnance Survey data covered by OS OpenData Licence.
</p>
Expand Down
65 changes: 47 additions & 18 deletions js/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
PetitionMap.mp_data = PetitionMap.mp_data || undefined;
PetitionMap.population_data = PetitionMap.population_data || undefined;
PetitionMap.current_area = PetitionMap.current_area || undefined;
PetitionMap.map_mode = PetitionMap.map_mode || undefined;
PetitionMap.signature_buckets = PetitionMap.signature_buckets || undefined;
PetitionMap.weighted_current_petition = PetitionMap.weighted_current_petition || undefined;
PetitionMap.is_weighted = PetitionMap.is_weighted || true;
Expand Down Expand Up @@ -71,19 +72,34 @@
area = variables.area;
}
}

PetitionMap.current_area = area;

map_mode = 'topo';
if (variables.map_mode !== undefined) {
if (possibleMapModes().indexOf(variables.map_mode) !== -1) {
map_mode = variables.map_mode;
}
}
PetitionMap.map_mode = map_mode;

$('input[name=area][value=' + area + ']').prop('checked',true);
$('input[name=map_mode][value=' + map_mode + ']').prop('checked',true);
$('#petition_dropdown option[value=' + petition_id + ']').prop('selected', true);
return loadPetition(petition_id, false);
}

function possibleAreas() {
var possibleAreas = []
var possibleAreas = [];
$.each($('input[name=area]'), function(idx, elem) { possibleAreas[idx] = $(elem).attr('value'); });
return possibleAreas;
}

function possibleMapModes() {
var possibleMapModes = [];
$.each($('input[name=map_mode]'), function(idx, elem) { possibleMapModes[idx] = $(elem).attr('value'); });
return possibleMapModes;
}

// Extracts variables from url
function getURLVariables() {
var variables = {},
Expand Down Expand Up @@ -289,6 +305,16 @@
});
}

// Reset zoom and reload map with new mode
function changeMapMode() {
spinner.spin(target);
PetitionMap.map_mode = $("input[name='map_mode']:checked").val();
PetitionMap.resetMapState();
$.when(reloadMap()).then(function() {
pushstateHandler();
});
}

function changeColouring() {
var colouring = $("input[name='weighted']:checked").val();
if (colouring === "percentage") {
Expand All @@ -302,7 +328,7 @@

// Reload map
function reloadMap() {
var dataFile = 'json/uk/' + PetitionMap.current_area + '/topo_wpc.json';
var dataFile = 'json/uk/' + PetitionMap.current_area + '/' + PetitionMap.map_mode + '_wpc.json';
return $.when(PetitionMap.loadMapData(dataFile, 'wpc')).then(function() {
displayPetitionInfo();
$('#key').fadeIn();
Expand Down Expand Up @@ -330,28 +356,25 @@
};

function highlightConstituencyFromDropdown() {
var ons_code = $("#constituency").val(),
constituency_data = {
"id": ons_code
};
var ons_code = $("#constituency").val();

$(window).trigger('petitionmap:constituency-on', constituency_data);
$(window).trigger('petitionmap:constituency-on', ons_code);
};

function selectConstituencyInDropdown(_event, constituency) {
$('#constituency option[value='+constituency.id+']').prop('selected', true);
function selectConstituencyInDropdown(_event, constituency_id) {
$('#constituency option[value='+constituency_id+']').prop('selected', true);
}

function displayConstituencyInfo(_event, constituency) {
var mpForConstituency = PetitionMap.mp_data[constituency.id];
var population = PetitionMap.population_data[constituency.id].population;
var percentage = PetitionMap.weighted_current_petition[constituency.id];
function displayConstituencyInfo(_event, constituency_id) {
var mpForConstituency = PetitionMap.mp_data[constituency_id];
var population = PetitionMap.population_data[constituency_id].population;
var percentage = PetitionMap.weighted_current_petition[constituency_id];

if (percentage !== undefined) {
if (PetitionMap.is_weighted) {
percentage = PetitionMap.weighted_current_petition[constituency.id] / 100;
percentage = PetitionMap.weighted_current_petition[constituency_id] / 100;
} else {
percentage = (PetitionMap.weighted_current_petition[constituency.id] / population) * 100;
percentage = (PetitionMap.weighted_current_petition[constituency_id] / population) * 100;
}
percentage = Math.round(percentage * 100) / 100;
} else {
Expand All @@ -365,7 +388,7 @@
data_found = false;

$.each(PetitionMap.current_petition.data.attributes.signatures_by_constituency, function(i, v) {
if (v.ons_code === constituency.id) {
if (v.ons_code === constituency_id) {
count = v.signature_count;
return;
}
Expand All @@ -381,7 +404,7 @@
}
}

function hideConstituencyInfo(_event, _constituency) {
function hideConstituencyInfo(_event, _constituency_id) {
//$('#constituency_info').show();
}

Expand Down Expand Up @@ -433,6 +456,9 @@
// Area selection
$("input[name='area']").on('change', changeArea);

// Map mode selection
$("input[name='map_mode']").on('change', changeMapMode);

// Weighted selection
$("input[name='weighted']").on('change', changeColouring);

Expand Down Expand Up @@ -467,6 +493,9 @@
if (PetitionMap.current_area !== undefined) {
state.area = PetitionMap.current_area;
}
if (PetitionMap.map_mode !== undefined) {
state.map_mode = PetitionMap.map_mode;
}
return state;
}

Expand Down
127 changes: 94 additions & 33 deletions js/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
PetitionMap.mp_data = PetitionMap.mp_data || undefined;
PetitionMap.population_data = PetitionMap.population_data || undefined;
PetitionMap.current_area = PetitionMap.current_area || undefined;
PetitionMap.map_mode = PetitionMap.map_mode || undefined;
PetitionMap.signature_buckets = PetitionMap.signature_buckets || undefined;
PetitionMap.weighted_current_petition = PetitionMap.weighted_current_petition || undefined;
PetitionMap.is_weighted = PetitionMap.is_weighted || true;
Expand Down Expand Up @@ -48,16 +49,20 @@

// Initialise map
function init(width, height) {
// a UK centric projection inspired by http://bost.ocks.org/mike/map/
projection = d3.geo.albers()
.center([0, 55.4])
.rotate([3.4, 0])
.parallels([50, 60])
.scale(5000)
.translate([width / 2, height / 2]);

path = d3.geo.path()
.projection(projection);
if (PetitionMap.map_mode === 'topo') {
// a UK centric projection inspired by http://bost.ocks.org/mike/map/
projection = d3.geo.albers()
.center([0, 55.4])
.rotate([3.4, 0])
.parallels([50, 60])
.scale(5000)
.translate([width / 2, height / 2]);

path = d3.geo.path()
.projection(projection);
} else if (PetitionMap.map_mode === 'hex') {
projection = path = undefined;
}

svg = d3.select("#map").append("svg")
.attr("width", width)
Expand All @@ -71,6 +76,24 @@

// Draw map on SVG element
function draw(boundaries) {
// So that we can pan and zoom with the mouse while focused outside of the map (in the
// sea) we create a rectangle object covering the whole area so there is _something_ under
// the cursor which will trigger the event listener.
g.append("rect")
.attr("class", "map-background")
.attr("x", -width)
.attr("y", -height)
.attr("width", width * 3)
.attr("height", height * 3);

if (PetitionMap.map_mode === "topo") {
drawTopo(boundaries);
} else if (PetitionMap.map_mode === "hex") {
drawHex(boundaries);
}
}

function drawTopo(boundaries) {
if (PetitionMap.current_area === "uk") {
projection.center([0, 55.4]);
} else if (PetitionMap.current_area === "eng") {
Expand All @@ -92,16 +115,6 @@

projection.scale(scale);

// So that we can pan and zoom with the mouse while focused outside of the map (in the
// sea) we create a rectangle object covering the whole area so there is _something_ under
// the cursor which will trigger the event listener.
g.append("rect")
.attr("class", "map-background")
.attr("x", -width)
.attr("y", -height)
.attr("width", width * 3)
.attr("height", height * 3);

// Add an area for each feature in the topoJSON (constituency)
g.selectAll(".area")
.data(topojson.feature(boundaries, boundaries.objects[units]).features)
Expand All @@ -110,8 +123,8 @@
.attr("id", function(d) {return d.id})
.attr("d", path)
.attr('vector-effect', 'non-scaling-stroke')
.on("mouseenter", function(constituency){ $(window).trigger('petitionmap:constituency-on', constituency); })
.on("mouseleave", function(constituency){ $(window).trigger('petitionmap:constituency-off', constituency); });
.on("mouseenter", function(constituency_boundary){ $(window).trigger('petitionmap:constituency-on', constituency_boundary.id); })
.on("mouseleave", function(constituency_boundary){ $(window).trigger('petitionmap:constituency-off', constituency_boundary.id); });

// Add a boundary between areas
g.append("path")
Expand All @@ -121,6 +134,57 @@
.attr('vector-effect', 'non-scaling-stroke');
}

function drawHex(hexjson) {
var hex_box_width = width * 0.95,
hex_box_height = height * 0.95;

var hexes = d3.renderHexJSON(hexjson, hex_box_width, hex_box_height);

var hex_half_width = hexes[0].vertices[1].x,
hex_radius = hexes[0].vertices[3].y;

var xmax = d3.max(hexes, function (h) { return +h.x }),
ymax = d3.max(hexes, function (h) { return +h.y }),
xmin = d3.min(hexes, function (h) { return +h.x }),
ymin = d3.min(hexes, function (h) { return +h.y });

var hex_top = ymin - hex_radius,
hex_bottom = ymax + hex_radius,
hex_left = xmin - hex_half_width,
hex_right = xmax + hex_half_width;

var hexes_width = hex_right - hex_left,
hexes_height = hex_bottom - hex_top;

var margin_x = (width - hexes_width) / 2,
margin_y = (height - hexes_height) / 2;

var hexmap = g.selectAll(".hex-area")
.data(hexes)
.enter()
.append("g")
.attr("class", "hex-area")
.attr("id", function(hex) { return "hex-"+hex.key; })
.attr("transform", function(hex) {
return "translate(" + (margin_x + hex.x) + "," + (margin_y + hex.y) + ")";
})

// Draw the polygons around each data hex's centre
hexmap
.append("polygon")
.attr("points", function(hex) { return hex.points; })
.attr("class", "area")
.attr("id", function(hex) { return hex.key; })
.attr('vector-effect', 'non-scaling-stroke')
.on("mouseenter", function(constituency_hex){ $(window).trigger('petitionmap:constituency-on', constituency_hex.key); })
.on("mouseleave", function(constituency_hex){ $(window).trigger('petitionmap:constituency-off', constituency_hex.key); });

hexmap
.append("polygon")
.attr("points", function(hex) { return hex.points; })
.attr("class", "boundary")
.attr('vector-effect', 'non-scaling-stroke')
}

////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -175,19 +239,19 @@

// Show constituency info and party colours on select
// (hover on desktop or click on mobile)
function highlightConstituencyOnMap(_event, constituency) {
var mpForConstituency = PetitionMap.mp_data[constituency.id],
function highlightConstituencyOnMap(_event, constituency_id) {
var mpForConstituency = PetitionMap.mp_data[constituency_id],
party_class = stripWhitespace(mpForConstituency.party);
deselectPartyColours();
d3.select("#" + constituency.id).classed(party_class, true);
d3.select("#" + constituency.id).classed("selected_boundary", true);
d3.select("#" + constituency_id).classed(party_class, true);
d3.select("#" + constituency_id).classed("selected_boundary", true);
}

// Remove classes from other constituencies on deselect
function dehighlightConstituencyOnMap(_event, constituency) {
// var party_class = stripWhitespace(PetitionMap.mp_data[constituency.id].party);
// d3.select("#" + constituency.id).classed(party_class, false);
// d3.select("#" + constituency.id).classed("selected_boundary", false);
function dehighlightConstituencyOnMap(_event, constituency_id) {
// var party_class = stripWhitespace(PetitionMap.mp_data[constituency_id].party);
// d3.select("#" + constituency_id).classed(party_class, false);
// d3.select("#" + constituency_id).classed("selected_boundary", false);
}

// Removes all other party colour classes from constituencies
Expand Down Expand Up @@ -235,7 +299,6 @@
factor = 0.2,
target_zoom = 1,
center = [width / 2, height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate(),
translate0 = [],
l = [],
Expand All @@ -245,8 +308,6 @@
direction = (this.id === 'zoom_in') ? 1 : -1;
target_zoom = zoom.scale() * (1 + factor * direction);

if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }

translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
Expand Down
Loading