Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stuwilmur/874b40f364dd4cc50be3c1493487542d to your computer and use it in GitHub Desktop.
Save stuwilmur/874b40f364dd4cc50be3c1493487542d to your computer and use it in GitHub Desktop.
Scotland terminations categorised

termination-categorised

Scotland termination statistics; visualisation of different categorisations.

HB16CD HB16NM OBJECTID
S08000015 Ayrshire and Arran 0
S08000016 Borders 1
S08000017 Dumfries and Galloway 2
S08000018 Fife 3
S08000019 Forth Valley 4
S08000020 Grampian 5
S08000021 Greater Glasgow and Clyde 6
S08000022 Highland 7
S08000023 Lanarkshire 8
S08000024 Lothian 9
S08000025 Islands 10
S08000027 Tayside 12
S99999999 All Areas 11
S00000000 Other / Unknown 13
<!DOCTYPE html>
<html lang="en">
<head>
<title>Scotland termination figures</title>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Color Scale -->
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<!-- Font -->
<link href="https://fonts.googleapis.com/css?family=Merriweather&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Merriweather', serif;
}
.yaxis {
font-family: 'Merriweather', serif;
}
.xaxis {
font-family: 'Merriweather', serif;
}
.notes {
font-size : 75%;
display : none;
width : 70%;
}
.ylabel {
font-size : 80%;
}
.caption {
font-size : 130%;
}
.subcaption {
font-size : 80%;
}
a {
text-decoration: none;
color : black;
}
a:hover {
text-decoration: underline;
}
a:active {
color: black;
}
a:visited {
color: black;
.tooltip {
pointer-events: none;
}
}
</style>
</head>
<body>
<!-- Initialize menus for the measure and the board -->
<select id="measureButton">abc</select>
<select id="boardButton"></select>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<p class="caption">Termination of pregnancy in Scotland across health board categorised by age, deprivation, estimated gestation, method and grounds for termination.</p>
<p class="subcaption">Data from the <a href="https://beta.isdscotland.org/find-publications-and-data/population-health/sexual-health/termination-of-pregnancy-statistics/">Termination of Pregnancy report, Public Health Scotland Data and Intelligence, published 25 August 2020</a>.</p>
<button type="button" id="notesButton">Show/hide notes</button>
<div class="notes">
<ul>
<li>Terminations of pregnancy refers to therapeutic abortions notified in accordance with the Abortion Act 1967.</li>
<li> N.B. Some data are provisional. For display purposes, items for which there are no data available or where the value has been surpressed due to the potential risk of disclosure appear as as zeros.</li
<li>Islands describes Orkney, Shetland and Western Isles NHS Board areas.</li>
<li>Other/unknown describes patients resident outwith Scotland or Scottish residents who cannot be assigned to an NHS Board.</li>
<li>For each year the most appropriate SIMD release was used: 2009 uses SIMD 2009; 2010 to 2013 uses SIMD 2012; 2014 onwards uses SIMD 2016. Further information about SIMD can be found at <a href = "http://www.isdscotland.org/Products-and-Services/GPD-Support/Deprivation/SIMD/"> idscotland SIMD</a>.</li>
<li>Some cases could not be assigned to an SIMD quintile.</li>
<li>Grounds for abortion: as some notifications record more than one Statutory Ground, the numbers and percentages of Grounds may exceed the total number of pregnancies.</li>
<li>See Glossary for definitions of Statutory Grounds.</li>
<li>On 1st April 2014, NHS Board boundaries were changed to align with local authorities to allow them to work closer together in the provision of care in the local community. To allow direct comparisons over time between NHS boards, this alignment has been applied to pre-2014 data. Further information is available at <a href=" http://www.isdscotland.org/Products-and-Services/GPD-Support/Geography/NHS-Board-Boundary-Changes/">idscotland NHS Board Changes</a>.</li>
<li>Source : Notifications (to the Chief Medical Officer for Scotland) of abortions performed under the Abortion Act 1967, ISD.</li>
</ul>
</div>
<script>
var codesMap = new Map();
var measuresMap = new Map();
// set the dimensions and margins of the graph
var margin = {top: 20, right: 100, bottom: 30, left: 70},
width = 1200 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
chartWidth = width / 2;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// create a tooltip
var Tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "0px")
.style("padding", "5px")
.style("position", "absolute")
.style("font-family", "Merriweather")
.style("font-size", "80%")
// create a tooltip for the map
var mapTooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0.1)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "none")
.style("border-width", "1px")
.style("border-radius", "0px")
.style("padding", "5px")
.style("position", "absolute")
.style("font-family", "Merriweather")
.style("font-size", "80%")
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function(d) {
Tooltip
.style("opacity", 1)
}
var mousemove = function(d) {
Tooltip
.html(d.key)
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
labelArea(d);
}
var mouseleave = function(d) {
Tooltip
.style("opacity", 0)
d3.select("svg").selectAll("text.data").remove();
}
// Three function that change the map tooltip when user hover / move / leave a feature
var mapmouseover = function(d) {
mapTooltip
.style("opacity", 1)
d3.select(this)
.style('fill', colours.teal)
.style("opacity", d.properties.HBCode == selectedBoard ? 1 : 0.5)
}
var mapmousemove = function(d) {
mapTooltip
.html(codesMap.get(d.properties.HBCode))
.style("left", (d3.mouse(this)[0] + 20) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mapmouseleave = function(d) {
mapTooltip
.style("opacity", 0)
d3.select(this)
.style('fill', getRegionColour(d))
.style("opacity", 1)
}
var mapmouseclick = function(d) {
d3.select("#boardButton")
.property('value', d.properties.HBCode);
optionsAndUpdate();
}
var projection = d3.geoMercator()
.scale(2600)
.translate([width, height / 2])
.center([-4, 58]);
var geoGenerator = d3.geoPath()
.projection(projection);
function getRegionColour(d) {
return d.properties.HBCode == selectedBoard ? colours.teal : colours.grey;
}
function updateGeo(geojson) {
var u = d3.select('svg')
.selectAll('path.region')
.data(geojson.features);
u.enter()
.append('path')
.attr("class", "region")
.attr('d', geoGenerator)
.style("fill", getRegionColour)
.on("mouseover", mapmouseover)
.on("mousemove", mapmousemove)
.on("mouseleave", mapmouseleave)
.on("click", mapmouseclick)
}
function updateRegionColours()
{
var u = d3.select('svg')
.selectAll('path.region')
.transition()
.duration(500)
.style("opacity", 1)
.style("fill", getRegionColour);
}
function formatX(x)
{
if (x.toFixed(0) == x)
return x;
else
return x.toFixed(1);
}
function toggleNotes()
{
var n = d3.selectAll(".notes");
var newDisplay = n.style("display") == "block" ? "none" : "block";
n.transition()
.duration(durationTime)
.style("display",newDisplay);
}
var colours = { teal : "#69b3a2", grey : "#aaa" };
var nestedData;
var line;
var dot;
var x;
var y;
var selectedRegion;
var yLabel;
var chartLabel;
var geojson;
var selectedMeasure = 'Age';
var selectedBoard = 'S08000020';
var nestedData;
var catNestedData;
var measures;
var durationTime = 1000; //1s
var yScale;
var xScale;
var rectSize = 20;
var rectMargin = 10;
function optionsAndUpdate()
{
// recover the options that have been chosen
selectedMeasure = d3.select("#measureButton").property("value");
selectedBoard = d3.select("#boardButton").property("value");
// run the updateChart function with this selected option
update(selectedMeasure, selectedBoard);
}
function labelArea(d)
{
var labels = d3.select("svg").selectAll("text.data");
labels.data(d)
.enter()
.append("text")
.merge(labels)
.attr("class","data")
.attr("x", function(d,i) {return xScale(i);})
.attr("y", function(d) {return yScale(d[1]);})
.text(function(d,i) { return formatX(d[1]-d[0]);})
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("font-size", "80%")
.style("pointer-events", "none");
labels.exit().remove();
}
// A function that updates the chart
function update(measure, board) {
updateRegionColours();
var boardData = nestedData.filter(function(e)
{
return e['key'] == board;
});
var categoryData = boardData[0].values.filter(function(f)
{ return f['key'] == measure;
})
var allCategories = catNestedData.filter(function(f)
{ return f['key'] == measure;
});
var categories = allCategories[0].values.map( function(d) { return d.key; });
color = d3.scaleOrdinal()
.domain(categories)
.range(d3.schemeSet3)
var numYears = categoryData[0].values.length - 1;
var dates = categoryData[0].values.map(function(d) {return +d.key; })
var firstDate = new Date(dates[0],0,0);
var lastDate = new Date(dates[dates.length - 1],0,1); // add a day to make sure the scale hits the last year
var merged = categoryData[0].values.map(function(d) {var obj = d.values.reduce(function(acc, cur, i) {acc[cur.Group] = cur.Value; return acc;}, {}); obj.Year = d.key; return obj;})
updateGeo(geojson);
var areaGenerator = d3.area()
.x(function(d, i) {
return xScale(i);
})
.y0(function(d) {
return yScale(d[0]);
})
.y1(function(d) {
return yScale(d[1]);
});
var stacker = d3.stack()
.keys(categories)
var stackedData = stacker(merged);
yScale = d3.scaleLinear()
.domain([0, d3.max(stackedData, d => d3.max(d, d => d[1]))]).nice()
.range([height, 0]);
xScale = d3.scaleTime().domain([0, numYears]).range([0, chartWidth]);
var xScaleDate = d3.scaleTime().domain([firstDate, lastDate]).range([0, chartWidth]);
var w = d3.select('g').selectAll('path.layers')
.data(stackedData)
w.enter()
.append('path')
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave",mouseleave)
.merge(w)
.transition()
.duration(durationTime)
.attr('class', 'layers')
.style('fill', function(d, i) {
return color(d.key);
})
.attr('d', areaGenerator)
w.exit()
.transition()
.duration(durationTime)
.style('fill', 'none')
.remove();
d3.select('g')
.transition()
.duration(durationTime)
.call(d3.axisLeft(yScale));
d3.selectAll('.xscale')
.remove();
var xscaleOffset = height + margin.bottom;
d3.select('svg')
.append("g")
.attr("class", "xscale")
.attr("transform", "translate("+margin.left+"," + xscaleOffset + ")")
.call(d3.axisBottom(xScaleDate));
var legend = d3.select("g").selectAll("rect.legend")
.data(stackedData)
legend.enter()
.append("rect")
.merge(legend)
.transition()
.duration(durationTime)
.attr("class","legend")
.style("fill", function(d,i) {
return color(d.key);
})
.attr("width", rectSize)
.attr("height", rectSize)
.attr("x", chartWidth + rectMargin)
.attr("y", function(d,i) {return i * (rectSize + rectMargin);});
legend.exit()
.transition()
.duration(durationTime)
.style('fill', 'none')
.remove();
var legendText = d3.select("g").selectAll("text.legendtext")
.data(stackedData)
legendText.enter()
.append("text")
.merge(legendText)
.transition()
.duration(durationTime)
.attr("class","legendtext")
.style("fill", function(d,i) {
//return color(d.key);
return "black";
})
.attr("x", chartWidth + rectSize + 2 * rectMargin)
.attr("y", function(d,i) {return i * (rectSize + rectMargin) + rectMargin;})
.style("font-family", "Merriweather")
.style("font-size", "120%")
.attr("text-anchor", "start")
.text(function(d) {return d.key;});
legendText.exit()
.transition()
.duration(durationTime)
.style('fill', 'none')
.remove();
// update y label
ylabel
.transition()
.duration(500)
.style("opacity",0)
ylabel
.transition()
.delay(500)
.duration(500)
.style("opacity",1)
.text(measuresMap.get(selectedMeasure).unit);
}
// Parse the csv file
function parseData(d) {
d.Year = +d.Year;
d.Value = d.Value == "*" ? 0 : d.Value = +d.Value; //!! handle the missing data.
return d;
}
//Read and parse the data
d3.csv('Health_Boards_May_2016_Names_and_Codes_in_Scotland.csv', function(lookup)
{
lookup.reduce(function(map, obj) {
codesMap.set(obj.HB16CD, obj.HB16NM);
}, {});
var codesArray = Array.from(codesMap);
d3.json('sco_nhs_boards.geojson', function(err, json) {
d3.csv("terminations data.csv", parseData, function(theData) {
d3.csv("measures_descriptors.csv", function(measures) {
measures.reduce(function(map, obj) {
measuresMap.set(obj.measure, obj);
}, {});
geojson = json;
nestedData = d3.nest()
.key(function(d) { return d.ID; })
.key(function(d) { return d.Measure; })
.key(function(d) { return d.Year; })
//.key(function(d) { return d.Group; })
.entries(theData);
catNestedData = d3.nest()
.key(function(d) { return d.Measure; })
.key(function(d) { return d.Group; })
.entries(theData);
measures = catNestedData.map( function(d) { return d.key; })
// add the options to the measures button
d3.select('#measureButton')
.on('change', function(d)
{
optionsAndUpdate();
//!!
})
.selectAll('option')
.data(measures)
.enter()
.append('option')
.attr('value', function(d) {return d;})
.text(function(d) {return measuresMap.get(d).description;});
// add the options to the boards button
d3.select('#boardButton')
.on('change', function(d)
{
optionsAndUpdate();
//!!
})
.selectAll('option')
.data(codesArray)
.enter()
.append('option')
.attr('value', function(d) {return d[0];})
.text(function(d) {return d[1];});
d3.select('#notesButton')
.on('click', toggleNotes);
var measure = d3.select("#measureButton").property("value");
var board = d3.select("#boardButton").property("value");
var boardData = nestedData.filter(function(e)
{
return e['key'] == board;
});
var categoryData = boardData[0].values.filter(function(f)
{ return f['key'] == measure;
})
var allCategories = catNestedData.filter(function(f)
{ return f['key'] == measure;
});
var categories = allCategories[0].values.map( function(d) { return d.key; });
color = d3.scaleOrdinal()
.domain(categories)
.range(d3.schemeSet3)
var numYears = categoryData[0].values.length;
var merged = categoryData[0].values.map(function(d) {var obj = d.values.reduce(function(acc, cur, i) {acc[cur.Group] = cur.Value; return acc;}, {}); obj.Year = d.key; return obj;})
ylabel = d3.select("svg")
.append("text")
.attr("class", "ylabel")
.attr("text-anchor", "start")
.attr("alignment-baseline", "hanging")
.attr("x",-height/2 - margin.top)
.attr("y",5)
.attr("transform", "rotate(-90)")
.text(measuresMap.get(selectedMeasure).unit);
updateGeo(geojson);
optionsAndUpdate();
})
})
})
})
</script>
</body>
</html>
measure description unit
Age Age of woman Number
Gestation Estimated gestation (completed weeks) Cumulative percentage
Grounds Grounds for abortion Number
Method Method of termination Percentage
Previous Previous terminations Rate per 1000 women age 15-44
SIMD Deprivation (SIMD) quintile Rate per 1000 women age 15-44
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.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment