Public
Edited
Dec 15, 2022
Insert cell
Insert cell
Insert cell
chart = {
// set the width and height
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;
// create an svg element
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();

// The container for all the bars
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);

}
});
}
Insert cell
dataInput = fetch("https://raw.githubusercontent.com/abh80/olympic-medals-2020/master/data.json").then((response) => response.json())
Insert cell
d3 = require("d3@7")
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more