Published
Edited
Apr 9, 2022
Insert cell
# Trending Youtube Videos

Insert cell
Insert cell
chart = {
let height = 500, width = 360
let marginTop = 8, marginRight = 8, marginBottom = 30, marginLeft = 4;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("id","chart")
.attr("viewBox", [0, 0, width, height])

let yScale = d3.scaleBand()
.domain([0,1,2,3,4,5,6,7,8,9])
.range([height - marginBottom, marginTop])
let xMax = 1000000
let xScale = d3.scaleLinear()
.domain([0, xMax])
.range([marginLeft,width - marginRight])
let xAxis = d3.axisBottom(xScale).ticks(5).tickSizeOuter(0).tickFormat(d3.format(".1s"));
let yAxis = d3.axisLeft(yScale).ticks(0).tickSize(0).tickFormat("");
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis)
.attr("id","xAxis")
.call(g => g.append("text")
.attr("x", width-marginRight)
.attr("y", 30)
//.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text("Views"));
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.attr("id","yAxis")
.call(yAxis)
let monthData;
if(userInputs.month === "all"){
monthData = youtubeUS.filter(d =>
d.categoryId === +userInputs.categoryId)
}
else{
monthData = youtubeUS.filter(d =>
d3.timeFormat("%Y-%m-%d")(d.publishedAt).includes(`2021-${userInputs.month}`)
&& d.categoryId === +userInputs.categoryId)
}
monthData.sort((a,b)=> a.view_count < b.view_count ? -1 : 1)
if(monthData.length > 10){
monthData = monthData.slice(monthData.length - 10, monthData.length)
}
let barHeight = (height - marginTop - marginBottom) / 10 - 2;
for(let index=0; index<monthData.length; index++) {
let timeout = setTimeout(()=>{
// rescale y axis if necessary
if(+monthData[index].view_count > xMax){
xMax = 1 * monthData[index].view_count;
xScale = d3.scaleLinear().domain([0, xMax]).range([marginLeft,width - marginRight])
xAxis = d3.axisBottom(xScale).ticks(5).tickSizeOuter(0).tickFormat(d3.format(".1s"));
svg.select("#xAxis").transition().duration(400).call(xAxis);
svg.selectAll(".bar").transition().duration(400)
.attr("width", d=> xScale(d.view_count) - marginLeft);
svg.selectAll(".bar-svg").transition().duration(400)
.attr("x",d => xScale(d.view_count) - (barHeight) * 240/135 - 2) ;
svg.selectAll(".channel-text").transition().duration(400)
.attr("x",d => (d.view_count > 0.6 * xMax)
? xScale(0) + 5
: xScale(d.view_count) + 5)
}

let bar = svg.selectAll(`#bar-${index}`)
.data([monthData[index]])
.join("rect")
.attr("id",`#bar-${index}`)
.classed("bar",true)
.attr("x",xScale(0))
.attr("y", yScale(9))
.attr("height", height * 0.5)
.attr("width", 0)
.on("mouseover",(e)=>{
})
.transition().duration(400)
.attr("width", d=> xScale(d.view_count) - marginLeft)
.transition().duration(400).delay(1800)
.attr("height", barHeight)
.attr("y", yScale(index)) .style("stroke","rgb(180,180,180)")
let imageSVG = svg.selectAll(`#image-${index}`)
.data([monthData[index]])
.join('svg')
.classed("bar-svg",true)
.attr("viewBox", "0 0 320 180")
.attr("height", 180)
.attr("width", 0)
.attr("preserveAspectRatio", "xMidYMid slice")
.attr("x",xScale(0))
.attr("y",yScale(9) +1)
imageSVG.transition().duration(400)
.attr("width", 320)
.attr("x",d => xScale(d.view_count) - 322)
.transition().duration(400).delay(1800)
.attr("y",yScale(index)+1)
.attr('width', (barHeight) * 16/9)
.attr('height', (barHeight -2))
.attr("x",d => xScale(d.view_count) - (barHeight) * 16/9 - 2)
imageSVG.append("image")
.attr("id",`image-${index}`)
.classed("bar-image",true)
.attr('xlink:href', monthData[index].thumbnail_link)
.attr('width', 320)
.attr('height', 240)
.attr("y","-30")

svg.selectAll(`#channel-${index}`)
.data([monthData[index]])
.join('text')
.attr("id",`channel-${index}`)
.classed("channel-text",true)
.text(d=> wrap_text_array(d.channelTitle, 30)[0])
.attr("x", xScale(0))
.attr("y",yScale(9) + 150)
.transition().duration(400)
.attr("x",width - 240)
.style("opacity",1)
.transition().duration(400).delay(1800)
.attr("x",d => xScale(0) + 5)
.attr("y", yScale(index) + (height - marginTop - marginBottom) / 20 + 5)

svg.selectAll(`#title-${index}`)
.data([monthData[index]])
.join('text')
.attr("id",`title-${index}-1`)
.classed("title-text",true)
.text(d=> wrap_text_array(d.title, 30)[0])
.attr("x", xScale(0))
.attr("y",yScale(9) + 165)
.transition().duration(400)
.attr("x",width - 240)
.style("opacity",1)
.transition().duration(400).delay(1800)
.attr("x",d => xScale(d.view_count) - (barHeight) * 240/135 - 10)
.attr("y", yScale(index) + (height - marginTop - marginBottom) / 20 + 5)
.style("opacity",0)
.remove()

svg.selectAll(`#title-${index}`)
.data([monthData[index]])
.join('text')
.attr("id",`title-${index}-2`)
.classed("title-text",true)
.text(d=> wrap_text_array(d.title, 30)[1])
.attr("x", xScale(0))
.attr("y",yScale(9) + 180)
.transition().duration(400)
.attr("x",width - 240)
.style("opacity",1)
.transition().duration(400).delay(1800)
.attr("x",d => xScale(d.view_count) - (barHeight) * 240/135 - 10)
.attr("y", yScale(index) + (height - marginTop - marginBottom) / 20 + 5)
.style("opacity",0)
.remove()

let toolTip = svg.append("g")
.attr("id",`tooltip-${index}`)
.classed(`tooltip`,true)
.attr("transform", `translate(${width - marginRight - 246}, ${yScale(9)})`).style("display","none")
.style("opacity",0)
toolTip.append("rect")
.attr("x", 0)
.attr("y",0)
.attr("height", height * 2/3)
.attr("width", 246)
.attr("fill","#f9f9f9")
.attr("stroke","none")

toolTip.append("text")
.classed("tooltip-title-text",true)
.text(d=> wrap_text_array(monthData[index].title, 38)[0])
.attr("x", 3)
.attr("y",yScale(9) + 165)

toolTip.append("text")
.classed("tooltip-title-text",true)
.text(d=> wrap_text_array(monthData[index].title, 38)[1])
.attr("x", width - marginRight - 243)
.attr("y",yScale(9) + 185)

toolTip.append("text")
.text(d=> wrap_text_array(monthData[index].channelTitle, 40)[0])
.attr("x", 3)
.attr("y",yScale(9) + 205)

toolTip.append("text")
.text("Trending for "+monthData[index].trending_days+" days")
.attr("x", width - marginRight - 243)
.attr("y",yScale(9) + 235)

toolTip.append("text")
.text("to "+d3.format(".2s")(monthData[index].view_count).replace("M"," million").replace("k"," thousand")+" views")
.attr("x", width - marginRight - 243)
.attr("y",yScale(9) + 255)

toolTip.append("text")
.text("Category: "+monthData[index].categoryId)
.attr("x", width - marginRight - 243)
.attr("y",yScale(9) + 285)

toolTip.append("text")
.text("(watch video below)")
.attr("x", width - marginRight - 243)
.attr("y",yScale(9) + 315)

toolTip.append('svg')
.classed("tooltip-image",true)
.attr("viewBox", "0 0 240 135")
.attr("height", 135)
.attr("width", 240)
.attr("preserveAspectRatio", "xMidYMid slice")
.attr("x", 3)
.attr("y", 3)
.append("image")
.attr('xlink:href', monthData[index].thumbnail_link)
.attr('width', 240)
.attr('height', 180)
.attr("y","-22.5")

/*
toolTip.append('foreignObject')
.attr("x", 3)
.attr("y", 3)
.attr("width",240)
.attr("height",135)
.append("body")
.attr("xmlns","http://www.w3.org/1999/xhtml")
.append("iframe")
.attr("sandbox","allow-scripts allow-same-origin allow-cross-origin allow-presentation")
.attr("width",240)
.attr("height",135)
.attr("src", `https://www.youtube.com/embed/p9LLoijPQfg`)
.attr("allow","autoplay; encrypted-media")
*/

svg.on("click",(e)=>{
let id = e.target.id;
console.log(id)
d3.selectAll(".tooltip")
.style("display","none")
.style("opacity",0)
if(id.includes("-")){
let idx = id.split("-")[1];

let tip = d3.selectAll("#tooltip-"+idx);
tip.raise();
tip
.style("display","block")
.style("opacity",1)
.transition()
.attr("x",4)

mutable video_id = monthData[idx].video_id
}
});
},3000 * index);
}

return svg.node()
}
Insert cell

viewof watch = html`<iframe width="560" height="315" id="youtube-embed"
allow="autoplay; encrypted-media" allowfullscreen
src="https://www.youtube.com/embed/${video_id}" frameborder="0"></iframe>
`
Insert cell
mutable video_id = "p9LLoijPQfg"
Insert cell
test = {
d3.selectAll("#youtube-embed")
.attr("src","https://www.youtube.com/embed/"+video_id)
}
Insert cell
function drawLine(path) {
path.transition()
.duration(1000)
.attrTween("stroke-dasharray", tweenDash)
.on("end", () => { d3.select(this).call(drawLine); });
}
Insert cell
function tweenDash() {
const l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l);
return function(t) { return i(t) };
}
Insert cell
wrap_text_array = (text, max_width) => {
const words = text.split(/\s+/).reverse();
let word,
lines = [ ],
line = [ ];
while (word = words.pop()) {
line.push(word);
if (line.join(" ").length > max_width) {
line.pop()
lines.push(line.join(" "));
line = [word];
}
}
lines.push(line.join(" "));
return lines;
}
Insert cell
style = html`<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
<style type="text/css">
#chart{
background-color: rgb(0,0,0);
color: rgb(180,180,180);
}
#xAxis line, #xAxis line{
stroke: white;
}
#xAxis text{
font-size: 14px;
}

#chart rect{
fill: rgb(90,90,90);
stroke: red;
}
form{
font-family: Roboto, Arial, sans-serif;
font-size: 14px;
font-weight: 400;
}
form select{
margin-right:10px;
}
.title-text {
fill: white;
font-family: Roboto, Arial, sans-serif;
font-size: 14px;
font-weight: 500;
text-anchor: start;
}
.channel-text {
fill: rgb(200,200,200 );
font-family: Roboto, Arial, sans-serif;
font-size: 12px;
text-anchor: start;
}
.tooltip text{
fill: rgb(200,200,200);
font-family: Roboto, Arial, sans-serif;
font-size: 14px;
font-weight: 400;
text-anchor: start;
}
.tooltip-title-text{
fill: white;
font-family: Roboto, Arial, sans-serif;
font-size: 14px;
font-weight: 500;
text-anchor: start;
}
.bar, .bar-image, .channel-text{
cursor: pointer;
}


</style>`
Insert cell
youtube24 = d3.csvParse(await FileAttachment("youtube-24@4.csv").text(), d3.autoType)
Insert cell
youtubeUS = d3.csvParse(await FileAttachment("youtube-US@1.csv").text(), d3.autoType)
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more