function drawChart() {
var markProgress = 0;
let currentSimId = 0;
const wrapper = d3.select("#wrapper")
.append("svg")
.attr("viewBox",[0, 0, 330, 330])
.attr("width", 300)
.attr("height", 300)
const bounds = wrapper.append("g")
.style("transform", `translate(${dimensions.margin.left}px,
${dimensions.margin.top}px)`)
bounds.append("g").attr("id","sankey")
bounds.append("g").attr("id","particles")
bounds.append("g").attr("id","stock-bars")
bounds.append("g").attr("id","catch-bars")
bounds.append("g").attr("id","catch-text")
bounds.append("g").attr("id","EEZ-labels")
//////////////////////////// Scales /////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
const yScale = d3.scaleLinear()
.domain([0,1])
.range([0, dimensions.boundedHeight])
.clamp(true)
const xScale = d3.scaleLinear()
.domain([0,countryIDs.length-1])
.range([0, dimensions.boundedWidth])
const linkLineGenerator = d3.line()
.y((d,i) => i * (dimensions.boundedHeight / 5))
.x((d,i) => i <= 2
? xScale(d[0])
: xScale(d[1])
)
.curve(d3.curveMonotoneX)
const yTransitionProgressScale = d3.scaleLinear()
.domain([0.35, 0.65])
.range([0,1])
.clamp(true)
/////////////////////////////////////////////////////////////////////////
//////////////////////////// Draw Sankey ////////////////////////////////
/////////////////////////////////////////////////////////////////////////
const linkOptions = d3.merge(
//For every EEZ
countryIDs.map(startID => (
//For every fleet
countryIDs.map(endID => (
//Return array
new Array(6).fill([startID, endID])
))
))
)
const links = d3.select("#sankey").selectAll(".category-path")
.data(linkOptions)
.enter().append("path")
.attr("class", "category-path")
.attr("id", d => `EEZ${d[0][0]}`)
.attr("d", linkLineGenerator)
.attr("stroke-width", dimensions.pathWidth)
.attr("opacity", 1);
//List of paths & ids to reference for particle tracing
let linksData = [];
d3.selectAll(".category-path").each(function(d){
let id = `${d[0][0]} > ${d[0][1]}`
linksData.push({id: id, path: this});
})
sankeyLabels(d3.select("#EEZ-labels").append("g").attr("id", "sankey-label-bottom"), dimensions.pathWidth, dimensions.labelHeight, x, dimensions.boundedHeight-20, "sankey-label-bottom")
sankeyLabels(d3.select("#EEZ-labels").append("g").attr("id", "sankey-label-top"), dimensions.pathWidth, dimensions.labelHeight, x, 0, "sankey-label-top")
let pathLength = linksData[0].path.getTotalLength()
/////////////////////////////////////////////////////////////////////////
//////////////////////////// Draw Particles /////////////////////////////
/////////////////////////////////////////////////////////////////////////
let sims = []
//Draw static stock-bar background
d3.select("#stock-bars").selectAll(".static-bar")
.data(EEZstocks)
.join("rect")
.attr("class", "static-bar")
.attr("id", function(d,i){return countryCodes[i]})
.attr("y", d => -reverseY(d.stock))
.attr("x", (d,i) => x(i))
.attr("height", d => reverseY(d.stock))
.attr("width", dimensions.pathWidth)
.attr("rx",2)
.style("fill", "rgba(211, 211, 211,0.6)")
let f2 = d3.format(".4s")
d3.select("#stock-bars")
.append("g").attr("id", "stock-pct")
.selectAll("text")
.data(EEZtonnage2)
.join("text").attr("id", function(d,i){return countryCodes[i]})
.attr("x", (d,i) => x(i) + dimensions.pathWidth/2)
.attr("y", d => d.EEZ == "United Kingdom" ? -reverseYtonn(d.stock) - 24 : -reverseYtonn(d.stock) - 8)
.text(d => Math.round(d.stock/1000))
.style("text-anchor", "middle")
.style("fill", "#C2C2C2")
.style("font-size", 13.5)
.style("font-family", "Lato")
.append("tspan")
.attr("dy","1.2em")
.attr("x",0)
.text(d => `${d.EEZ == "United Kingdom" ? "(k tonnes)" : ""}`)
.style("font-size", 12)
//Draw catch pct text
d3.select("#catch-text")
.selectAll("text")
.data(fleetLandingsPct(simulatedLandings),(d,i) => i)
.enter()
.append("text")
.text(d => f(d[1]))
.attr("x", (d,i) => x(i) + dimensions.pathWidth/2)
.attr("y", d => y(d[0]) + 18)
.style("text-anchor", "middle")
.style("fill", "#C2C2C2")
.style("font-size", 13.5)
.style("font-family", "Lato")
.style("opacity", 0);
//Update markers
function updateMarkers(tick, payoutInterval, elapsed, EEZ, maxSims) {
//If there are still sims to payout, add either 1 or 2 sims to the data store, depdning on how big the EEZ stock is //(payoutInterval)
if (mutable currentSimId < maxSims) {
sims = [
...sims,
...d3.range(payoutInterval < 1 ? 2 : 1).map(() => generateSim(elapsed, EEZ, simulatedLandings, currentSimId))
]
}
//Recompute the stacked bar chart segments
let stackedLandings = stackLandings(simulatedLandings)
.map(d => (d.forEach(v => v.index = d.index), d))
//Re-bind circles to data
const particles = d3.select("#particles").selectAll(".marker-circle")
.data(sims, d => d.id)
//Create circles for new sims
particles.enter().append("circle")
.attr("class", "marker marker-circle")
.attr("r", 2.3)
.style("opacity", 1)
.style("fill", d => colorScale(d.EEZ))
//Remoe spent sims
particles.exit().remove()
const markers = d3.selectAll(".marker")
//Update circle positions
markers.each(function(d){
let path = linksData.filter(l => l.id == d.linkID)[0].path
d.current = ((elapsed - d.startTime) / simSpeed) * path.getTotalLength() * d.speed
d.currentPos = path.getPointAtLength(d.current);
})
markers
.attr("cx", d => d.currentPos.x + d.xJitter)
.attr("cy", d => d.currentPos.y);
//Remove spent sims from data store
sims = sims.filter(d => (
d.currentPos.y < pathLength - dimensions.labelHeight/2
));
/////////////////////////////////////////////////////////////////////////
//////////////////////////// Draw EEZ Stock Bars ////////////////////////
/////////////////////////////////////////////////////////////////////////
d3.select("#stock-bars").selectAll(".shrinking-bar")
.data(EEZstocks)
.join("rect")
.attr("class", "shrinking-bar")
.attr("y", d => -reverseY(d.stock))
.attr("x", (d,i) => x(i))
.attr("height", d => reverseY(d.stock))
.attr("width", dimensions.pathWidth)
.attr("rx",2)
.style("fill", (d,i) => colorScale(i));
/////////////////////////////////////////////////////////////////////////
//////////////////////////// Draw Catch Bars //////////////////////////
/////////////////////////////////////////////////////////////////////////
d3.select("#catch-bars").selectAll("g")
.data(stackedLandings)
.join("g").attr("id", d => `${whiteSpace(d.key)}-segments`)
.selectAll("rect")
.data(v => v)
.join("rect")
.attr("x", (v,i) => x(countryNames.indexOf(v.data.fleet)))
.attr("y", v => y(v[0]))
.attr("height", v => y(v[1]) - y(v[0]))
.attr("width", dimensions.pathWidth)
.style("fill", (d,i) => colorScale((countryIDs.length-1) - d.index))
.attr("rx",3)
d3.select("#catch-text").selectAll("text")
.data(fleetLandingsPct(simulatedLandings), (d,i) => i)
.attr("y", d => y(d[0]) + 18)
.text(d => Math.round(d[1]/1000));
//Update the progress of the ultimate mark, to know when to trigger the next EEZ
markProgress = d3.min(sims,d => d.currentPos.y);
}
/////////////////////////////////////////////////////////////////////////
///////////////// Loops ///////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
let EEZ = 0
highlightEEZ(countryNames[EEZ])
function loopEEZs() {
//
let particleAnimations = function(){
let maxSims = EEZstocks[EEZ].stock
sims = []; //Clear sims data-store
mutable currentSimId = 0;
let duration = 180;
let payoutInterval = duration / maxSims;
let tick = 0;
markProgress = 0;
//Fade catch pct text in
if (EEZ == 0) {
d3.select("#catch-text").selectAll("text")
.transition().delay(simSpeed).duration(2500).style("opacity", 1);
}
let ticker = d3.timer(
function(elapsed){
tick ++
updateMarkers(tick, payoutInterval, elapsed, EEZ, maxSims)
let circlesComplete = markProgress >= pathLength - (dimensions.labelHeight/2) - 5;
//if all circles are dealt and there are more more EEZs to go, move onto next EEZ
if (EEZstocks[EEZ].stock <= 0 && circlesComplete && EEZ < 8) {
ticker.stop();
setTimeout(function(){loopEEZs(EEZ++)},1000);
// //Highlight focal EEZ labels
// d3.selectAll(".sankey-label-top").select("text").style("fill","#D3D3D3");
// d3.selectAll(".sankey-label-top").select("rect").style("stroke","#D3D3D3");
// let focalLabel = d3.select("#sankey-label-top").select(`#${countryCodes[EEZ]}`)
// focalLabel.select("text").style("fill","grey");
// focalLabel.select("rect").style("stroke","grey");
};
//if all circles are dealt and there are no more EEZs
if (EEZstocks[EEZ].stock <= 0 && circlesComplete && EEZ == 8) {
ticker.stop();
interactions();
}
})
}
//Colour each EEZ feature on iteration, then run particle animations
highlightEEZ(countryNames[EEZ], particleAnimations)
}
loopEEZs()
}