【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元素中显示歌曲信息。

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


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

相关推荐
su1ka1113 分钟前
re题(35)BUUCTF-[FlareOn4]IgniteMe
前端
测试界柠檬4 分钟前
面试真题 | web自动化关闭浏览器,quit()和close()的区别
前端·自动化测试·软件测试·功能测试·程序人生·面试·自动化
多多*5 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
2301_801074156 分钟前
TypeScript异常处理
前端·javascript·typescript
小阿飞_7 分钟前
报错合计-1
前端
caperxi9 分钟前
前端开发中的防抖与节流
前端·javascript·html
霸气小男9 分钟前
react + antDesign封装图片预览组件(支持多张图片)
前端·react.js
susu10830189119 分钟前
前端css样式覆盖
前端·css
学习路上的小刘11 分钟前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js
&白帝&11 分钟前
vue3常用的组件间通信
前端·javascript·vue.js