chart = {
let fullWidth = window.innerWidth;
let fullHeight = 0.5 * fullWidth;
let margins = {top: 20, bottom: 40, left: 50, right: 20};
let width = fullWidth - margins.left - margins.right;
let height = fullHeight - margins.top - margins.bottom;
let duration = 750;
let svg = d3.create("svg")
.attr("width", fullWidth)
.attr("height", fullHeight)
.style("background", "#CFCFCF");
let plotContainer = svg.append("g").attr("transform", `translate(${[margins.left, margins.top]})`);
let yAxisContainer = plotContainer.append("g").attr("transform", `translate(${[0,0]})`);
let render = (data, pivot) => {
let xScale = d3.scaleBand()
.domain(data.map(d => d.id))
.paddingInner(0.1)
.paddingOuter(0.2)
.align(0.5)
.range([0, width]);
let yScale = d3.scaleLinear().domain([0, d3.max(data, d => d.value)]).range([height, 0]).nice();
let barsContainer = plotContainer.selectAll("g.bar-container").data(data, d => d.id);
barsContainer
.enter()
.append("g")
.attr("class", "bar-container")
.attr("transform", (d,i) => `translate(${[xScale(d.id), yScale(0)]})`)
.transition()
.duration(duration)
.delay((d,i) => i * 100)
.attr("transform", (d,i) => `translate(${[xScale(d.id), yScale(0)]})`);
barsContainer
.transition()
.duration(duration)
.delay((d,i) => i * 100)
.attr("transform", (d,i) => `translate(${[xScale(d.id), yScale(0)]})`);
barsContainer
.exit()
.transition()
.duration(duration)
.delay((d,i) => i * 0)
.style("opacity", 0)
.remove();
let bars = plotContainer.selectAll("g.bar-container").selectAll("rect.bar").data((d) => [d]);
bars
.enter()
.append("rect")
.attr("class", "bar")
.attr("width", (d,i) => xScale.bandwidth())
.attr("height", (d,i) => 0)
.style("rx", 2)
.style("fill", "steelblue")
.style("opacity", 0)
.transition()
.duration(duration)
.delay((d,i) => i * 100)
.attr("transform", (d,i) => `translate(${[0, -(yScale(0) - yScale(d.value))]})`)
.attr("height", (d,i) => yScale(0) - yScale(d.value))
.style("opacity", 1);
bars
.style("fill", "steelblue")
.transition()
.duration(duration)
.delay((d,i) => i * 100)
.attr("transform", (d,i) => `translate(${[0, -(yScale(0) - yScale(d.value))]})`)
.attr("height", (d,i) => yScale(0) - yScale(d.value))
bars
.exit()
.transition()
.duration(duration)
.duration(duration)
.delay((d,i) => i * 100)
.style("opacity", 0)
.remove();
let diffBars = plotContainer.selectAll("g.bar-container").selectAll("rect.shadow-bar").data(d => [d]);
diffBars
.enter()
.append("rect")
.attr("class", "shadow-bar")
.attr("width", (d,i) => xScale.bandwidth())
.attr("height", 0)
.style("opacity", 0)
.style("rx", 2)
.transition()
.duration(duration)
.delay((d,i) => i * 100)
.attr("transform", (d,i) => `translate(${[0, -(yScale(0) - yScale(pivot)) - (d.diff > 0 ? 1 : 0) * Math.abs(yScale(0) - yScale(d.diff)) ]})`)
.attr("height", (d,i) => Math.abs(yScale(0) - yScale(d.diff)))
.style("fill", (d,i) => d.diff > 0 ? "firebrick" : "forestgreen" )
.style("opacity", 1)
diffBars
.transition()
.duration(duration)
.delay((d,i) => i * 100)
.attr("transform", (d,i) => `translate(${[0, -(yScale(0) - yScale(pivot)) - (d.diff > 0 ? 1 : 0) * Math.abs(yScale(0) - yScale(d.diff)) ]})`)
.attr("height", (d,i) => Math.abs(yScale(0) - yScale(d.diff)))
.style("fill", (d,i) => d.diff > 0 ? "firebrick" : "forestgreen" )
diffBars
.exit()
.transition()
.duration(duration)
.delay((d,i) => i * 100)
.style("opacity", 0)
.remove();
let diffTexts = plotContainer.selectAll("g.bar-container").selectAll("text.diff").data((d) => [d]);
diffTexts
.enter()
.append("text")
.attr("class", "diff")
.attr("text-anchor", "middle")
.attr("fill", "#FFF")
.attr("stroke", "#CCC")
.attr("stroke-width", 0.1)
.style("font-family", "Arial")
.style("font-size", "14")
.attr("dy", -7)
.attr("dx", xScale.bandwidth()/2)
.attr("data-value", 0)
.transition()
.duration(duration)
.ease(d3.easeSinInOut)
.attr("transform", (d,i) => `translate(${[0, -(yScale(0) - yScale(pivot)) - (d.diff > 0 ? 0 : -1) * Math.abs(yScale(0) - yScale(d.diff)) ]})`)
.textTween(function(d) {
const i = d3.interpolate(d3.select(this).attr("data-value"), d.div);
return function(t) { return d3.format(",.0%")(i(t)); };
})
.attr("data-value", (d) => d.div);
diffTexts
.transition()
.duration(duration)
.ease(d3.easeSinInOut)
.attr("transform", (d,i) => `translate(${[0, -(yScale(0) - yScale(pivot)) - (d.diff > 0 ? 0 : -1) * Math.abs(yScale(0) - yScale(d.diff)) ]})`)
.textTween(function(d) {
const i = d3.interpolate(d3.select(this).attr("data-value"), d.div);
return function(t) { return d3.format(",.0%")(i(t)); };
})
.attr("data-value", (d) => d.div);
diffTexts
.exit()
.transition()
.duration(duration)
.delay((d,i) => i * 0)
.style("fill", "transparent")
.remove();
let labels = plotContainer.selectAll("g.bar-container").selectAll("text.label").data((d) => [d]);
labels
.enter()
.append("text")
.attr("class", "label")
.attr("text-anchor", "middle")
.attr("fill", "#333")
.attr("stroke", "#FFF")
.attr("stroke-width", 0)
.style("font-family", "Arial")
.style("font-size", "14")
.attr("dy", 14)
.attr("dx", xScale.bandwidth()/2)
.transition()
.duration(duration)
.ease(d3.easeSinInOut)
.text((d,i) => d.id);
labels
.transition()
.duration(duration)
.ease(d3.easeSinInOut)
.text((d,i) => d.id);
labels
.exit()
.transition()
.duration(duration)
.delay((d,i) => i * 0)
.style("fill", "transparent")
.remove();
// Add scales to axis
let yAxis = d3.axisLeft()
.scale(yScale);
//Append group and insert axis
yAxisContainer
.transition()
.duration(duration)
.call(yAxis);
}
return Object.assign(svg.node(), {
update(mod = "total", deviateBy = "best-in-class", sorting = "ascending") {
let array = dataInput.medals.slice(0,20).map((d,i) => { d.id = `Site ${i+1}`; return d;});
let dMin = d3.min(array, d => d[mod]);
let dMax = d3.max(array, d => d[mod]);
let bestInClass = dMin;
let average = d3.mean(array, d => d[mod]);
let pivot = deviateBy === "best-in-class" ? bestInClass : average;
let data = [];
array.forEach((d,i) => {
let divergenceWithBestInClass = (d[mod] - bestInClass) / bestInClass;
let divergenceWithAverage = (d[mod] - average) / average;
let divergence = deviateBy === "best-in-class" ? divergenceWithBestInClass : divergenceWithAverage;
data.push({id: d.id, value: d[mod], div: divergence, diff: d[mod] - pivot});
})
data = data.sort((a,b) => sorting === "ascending" ? d3.ascending(a.div, b.div) : d3.descending(a.div, b.div))
render(data, pivot);
}
});
}