【d3.js实战】不同音乐流派的 Spotify 歌曲数据可视化2

不同音乐流派的 Spotify 歌曲数据可视化

上一篇文章:不同音乐流派的 Spotify 歌曲数据可视化,题目相同,换了一种写法,至于原因,你们应该懂。

我最近开发了一个音乐流派分析页面,展示了Spotify平台上不同音乐流派的歌曲数据。为了实现数据的处理和可视化,我使用了d3.js库。在这篇文章中,我将会分享d3.js库的基本使用方法和强大的功能,并介绍为什么选择使用它来进行数据可视化。

题目要求:

  1. 按要求分别输出数据集统计信息:

    1. 统计共有多少种不同音乐流派的歌曲数据;
    2. 统计共有多少位不同的歌手
    3. 统计音乐情感正面度(高于 0.5)有多少首歌曲
  2. 按照流派,将包含歌曲数量前 5 的流派,绘制柱状图,要求动态显示,并 为每个数据柱添加交互,显示本流派的基本信息(共有多少位歌手,以及流行度 高于 80 的歌曲有多少);

  3. 按照歌曲整体的响度,将响度前 20 的歌曲,绘制折线图,并为每个数据点 添加交互,显示该歌曲的歌手名,流行度和能量值。

D3.js是一种基于JavaScript的库,它提供了丰富的数据操作和可视化工具,可以帮助我们更好地处理和展示数据。在数据可视化方面,D3.js可以使数据更加生动、直观,从而更好地帮助我们理解和分析数据。

D3.js的特点体现在以下几个方面:

  1. 灵活性:D3.js允许我们对数据进行任意操作,可以帮助我们快速完成复杂的数据操作和可视化需求。我们可以使用D3.js来处理和转换各种类型的数据,例如数字、字符串、日期、地理位置等等,同时也可以根据自己的需求自定义数据操作。

  2. 可重用性:D3.js的代码非常模块化,可以重复使用代码中的某个部分,同时也可以把代码嵌入到其他项目中。这种可重用性使得D3.js非常适合用于大型项目的开发。

  3. 交互性: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相同的数据,只保留了一条。接着,我们对数据进行了三个方面的统计:

  1. 统计共有多少种不同音乐流派的歌曲数据;
  2. 统计共有多少位不同的歌手;
  3. 统计音乐情感正面度(高于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元素中显示歌曲信息。

最后,代码中添加了一个标题,用于描述折线图的含义。


过去不再,未来尚未,当下永恒。

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端