不同音乐流派的 Spotify 歌曲数据可视化
上一篇文章:不同音乐流派的 Spotify 歌曲数据可视化,题目相同,换了一种写法,至于原因,你们应该懂。
我最近开发了一个音乐流派分析页面,展示了Spotify平台上不同音乐流派的歌曲数据。为了实现数据的处理和可视化,我使用了d3.js库。在这篇文章中,我将会分享d3.js库的基本使用方法和强大的功能,并介绍为什么选择使用它来进行数据可视化。
题目要求:
按要求分别输出数据集统计信息:
- 统计共有多少种不同音乐流派的歌曲数据;
- 统计共有多少位不同的歌手
- 统计音乐情感正面度(高于 0.5)有多少首歌曲
按照流派,将包含歌曲数量前 5 的流派,绘制柱状图,要求动态显示,并 为每个数据柱添加交互,显示本流派的基本信息(共有多少位歌手,以及流行度 高于 80 的歌曲有多少);
按照歌曲整体的响度,将响度前 20 的歌曲,绘制折线图,并为每个数据点 添加交互,显示该歌曲的歌手名,流行度和能量值。
D3.js是一种基于JavaScript的库,它提供了丰富的数据操作和可视化工具,可以帮助我们更好地处理和展示数据。在数据可视化方面,D3.js可以使数据更加生动、直观,从而更好地帮助我们理解和分析数据。
D3.js的特点体现在以下几个方面:
-
灵活性:D3.js允许我们对数据进行任意操作,可以帮助我们快速完成复杂的数据操作和可视化需求。我们可以使用D3.js来处理和转换各种类型的数据,例如数字、字符串、日期、地理位置等等,同时也可以根据自己的需求自定义数据操作。
-
可重用性:D3.js的代码非常模块化,可以重复使用代码中的某个部分,同时也可以把代码嵌入到其他项目中。这种可重用性使得D3.js非常适合用于大型项目的开发。
-
交互性:D3.js可以帮助我们实现与用户的交互,让用户可以自由地选择和操作数据。例如,我们可以使用D3.js来创建交互式图表,让用户可以通过鼠标或触摸屏来探索数据。
D3.js是一种非常强大和灵活的数据可视化工具,可以帮助我们更好地理解和分析数据,同时也可以使我们的项目更加高效和可重用。
数据来源
该网页所呈现的数据源自Spotify API,其中包含了不同音乐流派的歌曲数据,包括歌曲数量、歌手数量以及情感正面度高于0.5的歌曲数量等关键信息。
d3.csv("Spotify歌曲数据集.csv").then(function (_data) {})
我们使用了d3.csv()函数来读取名为"Spotify歌曲数据集.csv"的CSV文件,并将读取到的数据传递给一个回调函数。回调函数中的参数_data代表读取到的数据,我们可以在这个函数中对数据进行处理和分析。
可视化效果
该页面利用了两个d3.js的可视化效果,它们分别是:
- 柱状图:该图展示了歌曲数量前五的音乐流派,让我们更加直观地了解不同音乐流派的流行程度。
- 折线图:该图展示了响度前20的歌曲,让我们更加直观地了解不同音乐流派中音乐的响度分布情况。
这两种可视化方式不仅能够帮助我们更好地理解数据,还可以根据自己的需求灵活选择不同的可视化方式,实现更优秀的数据展示效果。
分析数据
js
// 过滤掉track_id 相同的数据 只保留一条
let track_id = new Set()
let data = []
_data.forEach(item => {
if (!track_id.has(item.track_id)) {
track_id.add(item.track_id)
data.push(item)
}
});
console.log(data.length)
// 1 统计共有多少种不同音乐流派的歌曲数据;
let genre = new Set()
data.forEach(item => {
genre.add(item.track_genre)
});
d3.select("body")
.append("h2")
.text("共有" + genre.size + "种不同音乐流派的歌曲数据")
// 2 统计共有多少位不同的歌手
let artists = new Set()
data.forEach(item => {
let artist = item.artists.split(";")
artist.forEach(item => {
artists.add(item)
})
});
d3.select("body")
.append("h2")
.text("共有" + artists.size + "位不同的歌手")
// 3 统计音乐情感正面度(高于0.5)有多少首歌曲
let valence = 0
data.forEach(item => {
if (item.valence > 0.5) {
valence++
}
});
d3.select("body")
.append("h2")
.text("音乐情感正面度(高于0.5)有" + valence + "首歌曲")
我们首先对数据进行了过滤,过滤掉了track_id相同的数据,只保留了一条。接着,我们对数据进行了三个方面的统计:
- 统计共有多少种不同音乐流派的歌曲数据;
- 统计共有多少位不同的歌手;
- 统计音乐情感正面度(高于0.5)有多少首歌曲。
对于第一个问题,我们使用了Set数据结构来统计不同的音乐流派数量,并将其展示在页面上。对于第二个问题,我们同样使用了Set数据结构来统计不同的歌手数量,并将其展示在页面上。对于第三个问题,我们遍历了所有的歌曲数据,并统计了情感正面度高于0.5的歌曲数量。
在D3.js中,我们可以使用选择器来选中某个元素,并使用append()函数来添加新的元素。在这里,我们使用了d3.select()函数来选择body元素,并使用append()函数来添加新的h2元素,从而在页面上展示统计结果。
柱状图
js
let genreArr = []
let div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0.0)
.style("position", "absolute")
.style("background", "#5b5b5bd1")
.style("padding", "8px")
.style("border-radius", "4px")
.style("color", "white")
.style("transition", "all .2s ease-out")
.style("pointer-events", "none")
data.forEach(item => {
let flag = false
genreArr.forEach(item2 => {
if (item.track_genre == item2.genre) {
flag = true
item2.count++
item2.data.push(item)
}
})
if (!flag) {
genreArr.push({
count: 1,
genre: item.track_genre,
data: [item]
})
}
});
genreArr.sort((a, b) => {
return b.count - a.count
})
genreArr = genreArr.slice(0, 5)
console.log(genreArr);
// 显示本流派的基本信息(共有多少位歌手,以及流行度 高于 80 的歌曲有多少
genreArr.forEach(item => {
let artists = new Set()
let popularity = 0
item.data.forEach(item2 => {
let artist = item2.artists.split(";");
artist.forEach(item3 => {
artists.add(item3)
})
if (item2.popularity > 80) {
popularity++
}
})
item.artists = artists.size
item.popularity = popularity
})
console.log(genreArr);
// 绘制柱状图
let width = 800
let height = 400
let padding = {
top: 50,
bottom: 50,
left: 50,
right: 50
}
let svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
// 比例尺
let xScale = d3.scaleBand()
.domain(genreArr.map(item => item.genre))
.range([padding.left, width - padding.right])
.padding(0.5)
// 坐标轴
let xAxis = d3.axisBottom(xScale)
let yAxis = d3.axisLeft(yScale)
// 添加坐标轴
svg.append("g")
.attr("transform", "translate(0," + (height - padding.bottom) + ")")
.call(xAxis)
// 添加柱子
svg.selectAll("rect")
.data(genreArr)
.enter()
.append("rect")
.attr("x", (d, i) => {
return xScale(d.genre)
})
.attr("y", d => {
return height - padding.bottom
})
.attr("width", xScale.bandwidth())
.attr("height", d => {
return 0;
}
)
.attr("fill", "steelblue")
.on("mouseover", function (d, i) {
d3.select(this)
.attr("fill", "green");
// 显示流派信息
div
.style("opacity", 1);
div.html("流派: " + d.genre + "<br/>" +
"歌曲数量: " + d.count + "<br/>" +
"歌手数量: " + d.artists + "<br/>" +
"流行度高于80的歌曲数量: " + d.popularity + "<br/>"
)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mousemove", function (d, i) {
div
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.transition()
.duration(1000)
.delay((d, i) => {
return i * 100
})
.ease(d3.easeBounce)
.attr("y", d => {
return yScale(d.count)
})
.attr("height", d => {
return height - padding.bottom - yScale(d.count)
});
svg.append("text")
.attr("x", width / 2)
.attr("y", padding.top / 2 + 10)
.attr("text-anchor", "middle")
.attr("font-size", "24px")
.text("流派歌曲数量前5的流派")
首先,代码中定义了一个数组genreArr,用于存储每个流派对应的歌曲数量和所有歌曲数组。接着,遍历data数组中的每个元素,将其按照流派进行分类,并将分类后的数据存储到genreArr数组中。
然后,对genreArr数组进行处理,统计每个流派对应的歌手数量以及流行度高于80的歌曲数量。接着,定义了svg画布和比例尺、坐标轴等元素,并将柱子添加到svg画布中。为了增加交互,代码中定义了一个div元素,用于显示流派信息。通过鼠标移入、移出事件,实现了数据柱的交互效果,并在div元素中显示流派信息。
最后,代码中添加了一个标题,用于描述柱状图的含义。
折线图
js
let loudnessArr = []
data.forEach(item => {
loudnessArr.push({
loudness: item.loudness,
artists: item.artists,
popularity: item.popularity,
energy: item.energy
})
})
loudnessArr.sort((a, b) => {
return b.loudness - a.loudness
})
loudnessArr = loudnessArr.slice(0, 20)
console.log(loudnessArr);
// 绘制折线图
let width2 = 800
let height2 = 400
let padding2 = {
top: 50,
bottom: 50,
left: 50,
right: 50
}
let svg2 = d3.select("body")
.append("svg")
.attr("width", width2)
.attr("height", height2)
.style("border", "1px solid black")
// 比例尺
let yScale2 = d3.scaleLinear()
.domain([d3.min(loudnessArr, item => item.loudness), d3.max(loudnessArr, item => item.loudness)])
.range([height2 - padding2.bottom, padding2.top])
.nice()
// 坐标轴
let xAxis2 = d3.axisBottom(xScale2)
let yAxis2 = d3.axisLeft(yScale2)
// 添加坐标轴
svg2.append("g")
.attr("transform", "translate(" + padding2.left + ",0)")
.call(yAxis2)
// 添加折线
let line = d3.line()
.x((d, i) => {
return xScale2(i)
})
.y((d, i) => {
return yScale2(d.loudness)
})
svg2.append("path")
.attr("d", line(loudnessArr))
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", "2px")
// 添加数据点
svg2.selectAll("circle")
.data(loudnessArr)
.enter()
.append("circle")
.attr("cx", (d, i) => {
return xScale2(i)
})
.attr("cy", d => {
return yScale2(d.loudness)
})
.attr("r", 5)
.attr("fill", "steelblue")
.on("mouseover", function (d, i) {
d3.select(this)
.attr("fill", "red");
// 显示流派信息
div
.style("opacity", 1);
div.html("歌手: " + d.artists + "<br/>" +
"流行度: " + d.popularity + "<br/>" +
"能量值: " + d.energy + "<br/>"
)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mousemove", function (d, i) {
div
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function (d, i) {
d3.select(this)
.attr("fill", "steelblue");
div
.style("opacity", 0);
})
// 标题
svg2.append("text")
.attr("x", width2 / 2)
.attr("y", padding2.top / 2 + 10)
.attr("text-anchor", "middle")
.attr("font-size", "24px")
.text("歌曲整体的响度前20的歌曲")
首先,代码中定义了一个数组loudnessArr,用于存储每个歌曲的响度、歌手、流行度和能量值等信息。接着,遍历data数组中的每个元素,将其按照响度进行排序,并将排序后的前20个元素存储到loudnessArr数组中。
然后,定义了svg画布和比例尺、坐标轴等元素,并将折线和数据点添加到svg画布中。为了增加交互,代码中定义了一个div元素,用于显示歌曲信息。通过鼠标移入、移出事件,实现了数据点的交互效果,并在div元素中显示歌曲信息。
最后,代码中添加了一个标题,用于描述折线图的含义。
过去不再,未来尚未,当下永恒。