timelinePlot = {
var nTopics = 10
var chartData = data
let margin = ({top: 30, bottom: 50, left: 40, right: 100});
const width = 750
let height = nTopics*(20+5)
const outerDiv = d3.create("div").attr("id", "CovidTopicsTimeline").style("width",width + margin.left + margin.right+"px");
outerDiv.append("p").text("In the month of March, Twitter users kept a close watch on COVID-19 updates in the news and discussed major concerns on the platform. Below is an interactive visualization that lets you explore which topics received the most attention on Twitter every day in March. Hover over a topic to track its popularity over time.");
const controls = outerDiv.append("div").attr("class","controls")
const showMore = controls.append("button").text("show more").attr("value", nTopics)
const showLess = controls.append("button").text("show less").attr("value", nTopics)
let accountFilter = controls.append('select').attr("id", "filter-accounts");
const allMessages = [ {display: "All accounts", value: "all"},
{display: "Humans", value: "human"},
{display: "Bots", value: "bot"}]
const items = accountFilter
.selectAll('options')
.data(allMessages)
.enter()
.append('option')
.text(function (d) { return d.display; })
.attr("value", function (d) { return d.value; })
items.filter(function(d) {return d.value === "All accounts"}).attr("selected",true);
const container = outerDiv.append("svg").attr("width",width + margin.left + margin.right)
let curr_data = prepare_data(nTopics, chartData)
let xScale = d3.scalePoint()
.domain(d3.keys(data[0]).slice(0,31).map(x => +x))
.range([0,width])
let yScale = d3.scalePoint()
.domain([...Array(nTopics).keys()])
.range([0,height])
container.append("g")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisBottom(xScale)
.tickValues(xScale.domain().filter((d, i) => d % 5 === 0)));
const gridLines = container.append("g").attr("class", "vertical-grid")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", width).attr("height", height)
gridLines.append("line").attr("stroke", "#bfbeba").attr("stroke-width", 1).style("opacity",0)
var color = d3.scaleOrdinal()
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#dbaf2a','#a65628','#f781bf','#999999'])
const labelGroup = container.append("g").attr("class","topic-labels")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("height", height)
labelGroup.append("rect")
labelGroup.append("text").attr("dy", "15px").attr("dx", "5px")
const pathGroup = container.append("g").attr("class","topic-lines")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
pathGroup.append("path").attr("fill", "none").attr("stroke-width", 2).attr("class", "line")
const mouse_catcher = container.append("g").attr("class", "mouse-catcher")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("rect").attr("width", width).attr("height", height).attr("opacity", 0)
var tickwidth = xScale(2)
const dateHelper = container.append("g").attr("class", "date-interactive")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
const dateFeedbackRect = dateHelper.append("rect")
.attr("width", tickwidth).attr("height", height+30)
.attr("fill", "#e8e8e8").attr("opacity", "0.5")
.style("pointer-events", "none")
const dateLabel = dateHelper.append("g").attr("transform", "translate(0,"+(height+20)+")")
dateLabel.append("rect")
.attr("width", "100").attr("height", "20")
.attr("fill", "#000000").attr("rx",3).attr("ry",3).attr("x",(tickwidth-80)/2)
const dateLabelText = dateLabel.append("text").attr("class","dateText")
.attr("dy", "1.1em").attr("dx", "1em").text("March 12")
function follow_pointer(x) {
var w = xScale(2)
var date = Math.floor(x/w);
if (date >= 0){
dateLabelText.text("March " + (date+1))
}
x = Math.floor(x/w)*(w)-(w/2);
dateHelper.attr("transform", () => "translate(" + (margin.left + x) + "," + (margin.top-10) + ")")
}
follow_pointer(-100)
mouse_catcher.on("mouseenter mousemove", function(d){
var coordinates= d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
follow_pointer(x)
}).on("mouseleave", function(d){
follow_pointer(-100)
});
function drawChart(nTopics){
height = nTopics*(20+5)
container.attr("height", height + margin.top + margin.bottom)
let curr_data = prepare_data(nTopics, chartData)
yScale.domain([...Array(nTopics).keys()]).range([0,height])
var names = curr_data.map(function(d){ return d.key })
color.domain(names)
var w = xScale(2)
gridLines.selectAll("line")
.data([...Array(32).keys()])
.join("line").attr("class", "grid")
.attr("x1", d => xScale(d)-(w/2)).attr("x2", d => xScale(d)-(w/2))
.attr("y1", -10).attr("y2", height+10)
.attr("stroke", "#bfbeba").attr("stroke-width", 1)
let labels = labelGroup.selectAll("rect")
.data(curr_data)
.join("rect").attr("class", "labelContainer")
.attr("topic", d => d.selector)
.attr("x", width)
.attr("rank", d => d)
.attr("fill", function(d){ return color(d.key) })
let labelsText = labelGroup.selectAll("text")
.data(curr_data)
.join("text").attr("class", "labelText").attr("dy", "15px").attr("dx", "5px")
.text(d => d.values[30].rank +" " +d.key)
.attr("x", width)
labels.transition()
.ease(d3.easeCubicInOut)
.duration(1000)
.attr("y", d => yScale(d.values[30].rank)-10)
labelsText.transition()
.ease(d3.easeCubicInOut)
.duration(1000)
.attr("y", d => yScale(d.values[30].rank)-10)
pathGroup.selectAll("path")
.data(curr_data)
.join("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 2)
.attr("class", "line")
.attr("topic", d => d.selector)
.attr("d", function(d){
return d3.line()
.curve(d3.curveMonotoneX)
.x(function(d) { return xScale(d.date) })
.y(function(d) { return yScale(d.rank) })
(d.values)
})
labels.on("mouseover", function(d){
labelGroup.selectAll("rect").attr("class", "labelContainer background")
pathGroup.selectAll("path.line").attr("class", "line background")
this.setAttribute("class", "labelContainer")
pathGroup.select("path.line[topic="+this.getAttribute('topic')+"]").attr("class", "line")
}).on("mouseout", function(d){
labelGroup.selectAll("rect").attr("class", "labelContainer")
pathGroup.selectAll("path.line").attr("class", "line")
});
mouse_catcher.attr("height", height)
dateFeedbackRect.attr("height", height+30)
dateLabel.attr("transform", "translate(0,"+(height+20)+")")
}
drawChart(nTopics)
showMore.on("click", function(d) {
if (nTopics >= 50){
return
}
nTopics += 5
console.log("show more: " + nTopics)
drawChart(nTopics)
});
showLess.on("click", function(d) {
if (nTopics <= 5){
return
}
nTopics -= 5
console.log("show more: " + nTopics)
drawChart(nTopics)
});
accountFilter.on('change', function() {
var selectedText = d3.select('#filter-accounts option:checked').text();
var selectedValue = d3.select("#filter-accounts").property("value") ;
var url = "https://raw.githubusercontent.com/mashabelyi/Twitter-Covid-Response/master/data/topics_timeline_v2_"+selectedValue+".csv";
d3.csv(url).then(function(loaded) {
chartData = loaded
console.log(chartData)
drawChart(nTopics)
});
})
function set_date(date) {
let paths = pathGroup.selectAll("path")
.join("path")
paths
.attr("stroke-dasharray", function () {
console.log(this.getTotalLength())
return this.getTotalLength()
})
.attr("stroke-dashoffset", function () {
return this.getTotalLength()
})
.transition()
.duration(8000)
.attr("stroke-dashoffset", 0)
}
return outerDiv.node();
}