{
const margin = { top: 55, right: 125, bottom: 25, left: 125 };
const height = 760;
const color = d3.scaleOrdinal()
.range(['#DB7F85', '#50AB84', '#4C6C86', '#C47DCB', '#B59248', '#DD6CA7', '#E15E5A', '#5DA5B3', '#725D82', '#54AF52', '#954D56', '#8C92E8', '#D8597D', '#AB9C27', '#D67D4B', '#D58323', '#BA89AD', '#357468', '#8F86C2', '#7D9E33', '#517C3F', '#9D5130', '#5E9ACF', '#776327', '#944F7E']);
const name = nameToKey[area] || Object.keys(datasets)[0];
const dataset = datasets[name];
d3.selectAll('.plot > svg').remove();
const svg = d3.select('.plot').append('svg')
.style('width', `${width}px`)
.style('height', `${height}px`);
const data = dataset.data;
const all_keys = Array.from(dataset.all_keys);
const years = dataset.years;
const minYear = years[0];
const maxYear = years[years.length - 1];
const nb_keys = all_keys.size;
const xscale = d3.scaleLinear()
.domain([minYear, maxYear])
.range([margin.left, width - margin.right]);
const yscale = d3.scalePoint()
.domain(Object.keys(data[maxYear]).map((_, i) => i + 1))
.rangeRound([margin.top, height - margin.bottom]);
// Axis
const xaxis = d3.axisBottom()
.scale(xscale)
.tickFormat(d3.format(''));
const xaxis2 = d3.axisTop()
.scale(xscale)
.tickFormat(d3.format(''));
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (height - margin.bottom) + ')')
.call(xaxis);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (margin.top - 30) + ')')
.call(xaxis2);
const orderMinYear = Object.keys(data[minYear])
.map(t => [t, data[minYear][t].rank])
.sort((a, b) => a[1] - b[1])
.map(d => d[0]);
const orderMaxYear = Object.keys(data[maxYear])
.map(t => [t, data[maxYear][t].rank])
.sort((a, b) => a[1] - b[1])
.map(d => d[0]);
const visiblesMinYear = Object.keys(data[minYear])
.map(t => [t, data[minYear][t].percent])
.filter(a => a[1] && +a[1] >= 0.9)
.map(a => a[0]);
const visiblesMaxYear = Object.keys(data[maxYear])
.map(t => [t, data[maxYear][t].percent])
.filter(a => a[1] && a[1] >= 0.9)
.map(a => a[0]);
const firstYearVisible = {};
const lastYearVisible = {};
years.forEach((y) => {
all_keys.forEach((k) => {
if (data[y][k].percent >= 0.9 && !firstYearVisible[k]) {
firstYearVisible[k] = y;
}
if (data[y][k].percent >= 0.9) {
lastYearVisible[k] = y;
}
});
});
all_keys.forEach((k) => {
if (firstYearVisible[k] === years[0]) {
firstYearVisible[k] = null;
}
if (lastYearVisible[k] === years[years.length - 1] || lastYearVisible[k] === years[0]) {
lastYearVisible[k] = null;
}
});
const yAxisLeft = svg.append('g')
.attr('class', 'y axis yleft')
.attr('transform', 'translate(' + [margin.left - 30, 0]+ ')')
.call(d3.axisLeft()
.scale(d3.scalePoint()
.domain(orderMinYear)
.rangeRound([margin.top, height - margin.bottom])
));
yAxisLeft.selectAll('text')
.each(function (d) {
const k = this.innerHTML;
if (firstYearVisible[k]) {
const y = firstYearVisible[k];
const tx = xscale(y);
const ty = yscale(data[y][k].rank) - this.parentElement.transform.baseVal.getItem(0).matrix.f;
this.setAttribute('transform', `translate(${[tx - margin.left, ty]})`);
}
});
const yAxisRight = svg.append('g')
.attr('class', 'y axis yright')
.attr('transform', 'translate(' + [width - margin.left + 30, 0]+ ')')
.call(d3.axisRight()
.scale(d3.scalePoint()
.domain(orderMaxYear)
.rangeRound([margin.top, height - margin.bottom])
));
yAxisRight.selectAll('text')
.each(function (d) {
const k = this.innerHTML;
if (lastYearVisible[k]) {
const y = lastYearVisible[k];
const tx = width - xscale(y) - margin.left;
const ty = yscale(data[y][k].rank) - this.parentElement.transform.baseVal.getItem(0).matrix.f;
this.setAttribute('transform', `translate(${[-tx, ty]})`);
}
});
// Reset state of axis visibility to default
const resetVisibilityAxis = () => {
yAxisLeft
.selectAll('text')
.style('opacity', 1)
.style('font-weight', 400)
.style('fill', function (d) {
return visiblesMinYear.indexOf(this.innerHTML) > -1 ? 'black' : 'transparent';
});
yAxisRight
.selectAll('text')
.style('opacity', 1)
.style('font-weight', 400)
.style('fill', function (d) {
return visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 'black' : 'transparent';
});
};
// Highlight a specific entry on the axis
const highlightAxis = (name) => {
// svg.select('.yleft')
// .selectAll('text')
// .style('fill', function() { return name === this.innerHTML ? 'black' : visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 'black' : 'transparent'; })
// .style('opacity', function() { return name === this.innerHTML ? 1 : visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 0.3 : 1; })
// .style('font-weight', function() { return name === this.innerHTML ? 400 : visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 400 : 400; });
svg.select('.yleft')
.selectAll('text')
.style('fill', function() { return name === this.innerHTML && visiblesMaxYear.indexOf(name) > -1 ? 'black' : 'transparent'; })
.style('opacity', function() { return name === this.innerHTML ? 1 : visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 0.3 : 1; })
.style('font-weight', function() { return name === this.innerHTML ? 400 : visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 400 : 400; });
svg.select('.yright')
.selectAll('text')
.style('fill', function() { return name === this.innerHTML ? 'black' : visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 'black' : 'transparent'; })
.style('opacity', function() { return name === this.innerHTML ? 1 : visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 0.3 : 1; })
.style('font-weight', function() { return name === this.innerHTML ? 800 : visiblesMaxYear.indexOf(this.innerHTML) > -1 ? 400 : 400; });
};
resetVisibilityAxis();
// Add the lines
const group_tag = svg.selectAll('.tagline')
.data(all_keys)
.enter()
.append('g')
.attr('class', d => `tagline g_${d}`);
const line = d3.line()
.x(d => xscale(d[0]))
.y(d => yscale(d[1]))
.curve(d3.curveCatmullRom.alpha(0.66));
const radius = d3.scalePow().exponent(0.5)
.domain([0, 100])
.range([1, (width - margin.left - margin.right) / (years.length * 2.2)]);
group_tag.append('path')
.datum(d => d)
.attr('fill', 'none')
.attr('stroke', (d) => color(d))
.attr('stroke-linejoin', 'round')
.attr('stroke-linecap', 'round')
.attr('stroke-width', '1.75px')
.attr('d', (d) => splitArray(years.map(y => data[y][d].percent > 0 ? [y, data[y][d].rank] : null), null)
.filter(d => d.length > 1)
.map(d => line(d)));
// Add the bubbles
const g_circle = group_tag.append('g')
.selectAll('circle')
.data(years)
.enter();
g_circle.append('circle')
.attr('r', function(d) { return radius(data[d][this.parentElement.__data__].percent); })
.attr('cx', (d) => xscale(d))
.attr('cy', function(d) { return yscale(data[d][this.parentElement.__data__].rank); })
.attr('fill', function(d) { return color(this.parentElement.__data__); })
// Big transparent circle under each bubble to ease the mouseover on small bubbles
g_circle.append('circle')
.attr('r', 25)
.attr('fill', 'transparent')
.attr('cx', d => xscale(d))
.attr('cy', function (d) {
const tag = this.parentElement.__data__;
const v = data[d][tag];
return yscale(v.rank);
})
.on('mouseover', function (event, d) {
const tag = this.parentElement.__data__;
if (data[d][tag].percent === 0) return;
svg.selectAll(`._${d}.${tag}`).style('display', null);
svg.selectAll(`.tagline:not(.g_${tag})`).style('opacity', 0.3);
highlightAxis(tag);
})
.on('mouseout', function (d) {
svg.selectAll('.tooltip').style('display', 'none');
svg.selectAll(`.tagline`).style('opacity', 'initial');
resetVisibilityAxis();
});
// .on('click', function (d) {
// const tag = this.parentElement.__data__;
// highlightAxis(tag);
// svg.selectAll(`.tooltip.${tag}`).style('display', null);
// svg.selectAll(`.tagline:not(.g_${tag})`).style('opacity', 0.3);
// });
// Add all the labels on the bubbles now
// and set them to be undisplayed
group_tag.each(function (tag) {
g_circle.each(function (d) {
const v = data[d][tag];
if (v.percent !== 0) {
svg.append('text')
.attr('x', xscale(d))
.attr('y', yscale(v.rank) - 7)
.attr('class', `tooltip _${d} ${tag}`)
.attr('fill', 'black')
.attr('font-size', 14)
.attr('text-anchor', 'middle')
.attr('pointer-events', 'none')
.style('display', 'none')
.text(`${tag} - ${d}:`);
svg.append('text')
.attr('x', xscale(d))
.attr('y', yscale(v.rank) + 7)
.attr('class', `tooltip _${d} ${tag}`)
.attr('fill', 'black')
.attr('font-size', 14)
.attr('text-anchor', 'middle')
.attr('pointer-events', 'none')
.style('display', 'none')
.text(`${roundValue(v.percent, 1)}% / rank: ${v.rank}`);
}
});
});
}