Published
Edited
Apr 26, 2022
1 fork
1 star
Insert cell
Insert cell
Insert cell
subdata = FileAttachment("onlySupercategories-3.csv").csv()
Insert cell
duration = 3500
Insert cell
delay_amount = 1000
Insert cell
subdata.forEach(function(d,i) {
d.FirstDate = new Date(d.StartDate);
d.year = d.FirstDate.getFullYear();
d.date = new Date("01-01-" +d.year);
d.AwardNumber = d.AwardNumber.toString(10);
});

Insert cell
AwardsPerYear = Array.from(d3.rollup(subdata, v => v.length, d => d.date, v => v.AwardNumber))
Insert cell
Supercategories = new Set(subdata.map(d => d.SuperCategory))
Insert cell
subdata
Insert cell
newdata = Array.from(d3.rollup(subdata, v => v.length, d => d.date, d => d.SuperCategory)).sort(([a], [b]) => d3.ascending(a, b))
Insert cell
n = 12 //number of categories
Insert cell
function rank(value) {
const data = Array.from(Supercategories, name => ({name, value: value(name)}));
data.sort((a, b) => d3.descending(a.value, b.value));
for (let i = 0; i < data.length; ++i) data[i].rank = Math.min(n, i);
return data;
}
Insert cell
k=10 //interpolation for tween
Insert cell
new Date(year_select) == new Date(newdata[3][0])
Insert cell
new Date(year_select)
Insert cell
new Date(newdata[3][0])
Insert cell
keyframes = {
const keyframes = [];
for(var i=0; i < newdata.length; i++) {
newdata[i][2] = AwardsPerYear[i][1].size;
if(+new Date(year_select) == +new Date(newdata[i][0])){
//capture_index = i;
keyframes.push([
new Date(newdata[i][0]),
rank(name => (newdata[i][1].get(name) || 0)),
newdata[i][2]
]);
}
}

if(keyframes.length == 0){
keyframes.push([
new Date(newdata[0][0]),
rank(name => (newdata[0][1].get(name) || 0)),
newdata[0][2]
]);
}
// let ka, a, la;
// newdata.forEach (function([ka, a, la])/*, [kb, b, lb]]in newdata)*/ {
/* if(+new Date(year_select) != +new Date(ka)){
keyframes.push([
new Date(ka),
rank(name => (a.get(name) || 0)),
la
]);*/
//}})
//keyframes.push([new Date(kb), rank(name => b.get(name) || 0), lb]);
return keyframes;
}
Insert cell
nameframes = d3.groups(keyframes.flatMap(([, data]) => data), d => d.name)
Insert cell
Insert cell
Insert cell
function bars(svg) {
let bar = svg.append("g")
.attr("transform", `translate(0,${margin.top + barSize})`)
.attr("fill-opacity", 0.6)
.selectAll("rect");

return ([date, data], transition) => bar = bar
.data(data.slice(0, n), d => d.name)
.join(
enter => enter.append("rect")
.attr("fill", color)
.attr("height", y.bandwidth())
.attr("x", x(0))
.attr("y", d => y((prev.get(d) || d).rank))
.attr("width", d => x((prev.get(d) || d).value) - x(0)),
update => update,
exit => exit.transition(transition).remove()
.attr("y", d => y((next.get(d) || d).rank))
.attr("width", d => x((next.get(d) || d).value) - x(0))
)
.call(bar => bar.transition(transition)
.attr("y", d => y(d.rank))
.attr("width", d => x(d.value) - x(0)));

}
Insert cell
x = d3.scaleLinear([0, 1], [margin.left, width - margin.right])
Insert cell
y = d3.scaleBand()
.domain(d3.range(n + 1))
.rangeRound([90, 90 + barSize * (n + 1 + 0.1)])
.padding(0.1)
Insert cell
height = margin.top + barSize * (n+3) + margin.bottom
Insert cell
barSize = 48
Insert cell
margin = ({top: 16, right: 6, bottom: 6, left: 0})
Insert cell
color = {
const scale = d3.scaleOrdinal(d3.schemeTableau10);
if (subdata.some(d => d.category !== undefined)) {
const categoryByName = new Map(subdata.map(d => [d.name, d.category]))
scale.domain(Array.from(categoryByName.values()));
return d => scale(categoryByName.get(d.name));
}
return d => scale(d.name);
}
Insert cell
function projects(svg){
const now = svg.append("text")
.attr("text-anchor", "end")
.attr("x", width - 20)
.attr("y", height - (margin.bottom + barSize * (n + 1.2)))
.attr("dy", "0.08em")
.text("Number of projects:"+ keyframes[0][2]);
return ([date, cate, project], transition) => {
transition.end().then(() => now.text(" Number of projects:"+ project));
};
}
Insert cell
formatDate = d3.utcFormat("%Y")
Insert cell
function ticker(svg) {
const now = svg.append("text")
.style("font", `bold ${barSize}px var(--sans-serif)`)
.style("font-variant-numeric", "tabular-nums")
.attr("text-anchor", "end")
.attr("x", width - 6)
.attr("y", height - (margin.bottom + barSize * (n + 2)))
.attr("dy", "0.32em")
.text(formatDate(keyframes[0][0]));

return ([date, cate, project], transition) => {
transition.end().then(() => now.text(formatDate(date)));
};
}
Insert cell
/*function bubble(axisG) {
const now = svg
.append("circle")
.attr("fill", "black")
.attr("stroke", "none")
.attr("r", 3)
const getTicksDistance = (scale) => {
const ticks = scale.ticks();
const spaces = []
for(let i=0; i < ticks.length - 1; i++){
spaces.push(scale(ticks[i+1]) - scale(ticks[i]))
}
return spaces;
};

return ([date, cate, project], transition) => {
transition.end().then(() => now.attr("cx", )
};
}*/
Insert cell
function axis(svg) {
const g = svg.append("g")
.attr("transform", `translate(0,${margin.top + 130})`);

const axis = d3.axisTop(x)
.ticks(width / 160)
.tickSizeOuter(0)
.tickSizeInner(-barSize * (n + y.padding()));

return (_, transition) => {
g.transition(transition).call(axis);
g.select(".tick:first-of-type text").remove();
g.selectAll(".tick:not(:first-of-type) line").attr("stroke", "white");
g.select(".domain").remove();
};
}
Insert cell
function labels(svg) {
let label = svg.append("g")
.attr("transform", `translate(0,${margin.top + barSize})`)
.style("font", "bold 12px var(--sans-serif)")
.style("font-variant-numeric", "tabular-nums")
.attr("text-anchor", "end")
.selectAll("text");

return ([date, data], transition) => label = label
.data(data.slice(0, n), d => d.name)
.join(
enter => enter.append("text")
.attr("transform", d => `translate(${x((prev.get(d) || d).value)},${y((prev.get(d) || d).rank)})`)
.attr("y", y.bandwidth() / 2)
.attr("x", -6)
.attr("dy", "-0.25em")
.text(d => d.name)
.call(text => text.append("tspan")
.attr("fill-opacity", 0.7)
.attr("font-weight", "normal")
.attr("x", -6)
.attr("dy", "1.15em")),
update => update,
exit => exit.transition(transition).remove()
.attr("transform", d => `translate(${x((next.get(d) || d).value)},${y((next.get(d) || d).rank)})`)
.call(g => g.select("tspan").tween("text", d => textTween(d.value, (next.get(d) || d).value)))
)
.call(bar => bar.transition(transition)
.attr("transform", d => `translate(${x(d.value)},${y(d.rank)})`)
.call(g => g.select("tspan").tween("text", d => textTween((prev.get(d) || d).value, d.value))))
}
Insert cell
formatNumber = d3.format(",d")
Insert cell
function textTween(a, b) {
const i = d3.interpolateNumber(a, b);
return function(t) {
this.textContent = formatNumber(i(t));
};
}
Insert cell
Insert cell
mutable year_select = ""
Insert cell
year_select
Insert cell
chart_time = {
replay;

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

const updateBars = bars(svg);
const updateAxis = axis(svg);
const updateLabels = labels(svg);
const updateTicker = ticker(svg);
const updateProjects = projects(svg);
const axisG = svg.append("g")
.attr("transform", "translate(0,10)")
.call(d3.axisBottom(scale)
.tickFormat(d => d <= d3.timeYear(d) ? d.getFullYear() : null));

// Add the circles
axisG.selectAll(".tick")
.append("circle")
.attr("class", "tickcircle")
.attr("fill", "grey")
.attr("stroke", "none")
.attr("r", 5);
yield svg.node();

for (const keyframe of keyframes) {
const transition = svg.transition()
.duration(duration)
.delay(delay_amount)
.ease(d3.easeLinear);

// Extract the top bar’s value.
x.domain([0, keyframe[1][0].value]);
axisG.selectAll(".tickcircle").on("click", function(event){
// event.preventDefault();
mutable year_select = event.path[1].__data__;
console.log(d3.select(this).node())
d3.select(".selected").classed("selected", false);

// Select current item
d3.select(this).classed("selected", true);
d3.select(this).attr("fill", "blue");
})

updateAxis(keyframe, transition);
updateBars(keyframe, transition);
updateLabels(keyframe, transition);
updateTicker(keyframe, transition);
updateProjects(keyframe, transition);
invalidation.then(() => svg.interrupt());
await transition.end();
}
}
Insert cell
html`
.selected {
fill: red; important!
}
`
Insert cell
mutable barClickedName = ""
Insert cell
barClickedName
Insert cell
function clicked(event) {
event.preventDefault();
mutable barClickedName = event.target.dataset.year
}
Insert cell
year = Array.from(new Set(d3.map(subdata, d=>d.year)));
Insert cell
scale = d3.scaleTime().domain([new Date(2010, 12, 1), new Date(2019, 1, 1)]).range([50, width-50])
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