Published
Edited
Jun 9, 2020
1 fork
Insert cell
md`# Similar Song Network

Songs similar to one another according to [last.fm](http://www.last.fm/api) are linked together. Song nodes are sized based on playcounts, and colored by artist.

Data from [last.fm](http://www.last.fm/api/show/track.getSimilar). Some songs include additional links for effect.<br/>Popular songs are defined as those with playcounts above the median for all songs in network. This example is a simpler version of the [tutorial](http://flowingdata.com/2012/08/02/how-to-make-an-interactive-network-visualization/)</a> by [Jim Vallandingham](http://vallandingham.me/).

The root song of the tree is <b>${rootSong.name}</b>, and the artist is <b>${rootSong.artist}</b>.
`

Insert cell
//Obter um nó específico pelo seu índice
getRootNodeByIndice = (indice) => {
return dataset.nodes[indice]
}
Insert cell
//Função para obter o índice do nó que tem o máximo playcount
getIndiceMaxPlayCount = () => {
let maxPlayCount = 0
let indiceMaxPlayCount = 0
dataset.nodes.map((node, i)=> {
if(maxPlayCount<node.playcount) {
maxPlayCount = node.playcount
indiceMaxPlayCount = i
}
})
return indiceMaxPlayCount
}
Insert cell
//Obter o nó raiz
rootSong = getRootNodeByIndice(getIndiceMaxPlayCount())
Insert cell
buildvis = {
const width = 960
const height = 800
const svg = d3.select(DOM.svg(width, height))
.attr("viewBox", [-width / 2, -height / 2, width, height])
// Configure os nodes e os links
const nodes = dataset.nodes;
const links = dataset.links;
// Crie a constante simulation usando a função forceSimulation definida em outra célula
const simulation = forceSimulation(nodes, links).on("tick", ticked)
//Crie os elementos svg para os links e guarde-os em link
const link = svg.append("g")
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("class", "link");
//Crie os elementos svg para os nodes e guarde-os em node
const node = svg.append("g")
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("r",d => circleScale(d.playcount))
.call(drag(simulation))
node.append("title").text(d=> d.artist + " : " + d.name)
// Defina a função ticked
function ticked() {
link.attr("x1", d => d.source.x)
link.attr("y1", d => d.source.y)
link.attr("x2", d => d.target.x)
link.attr("y2", d => d.target.y)
node.attr("cx", d => d.x)
.attr("cy", d => d.y)
}
// Once we append the vis elments to it, we return the DOM element for Observable to display above.
return svg.node()
}
Insert cell
function drag(simulation){
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x
d.fy = d.y
}
function dragged(d) {
d.fx = d3.event.x
d.fy = d3.event.y
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0)
d.fx = null
d.fy = null
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
}
Insert cell
circleScale = d3.scaleSqrt()
.domain(d3.extent(dataset.nodes, d=>d.playcount))
.range([2,20])
Insert cell
function forceSimulation(nodes, links) {
return d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(50))
.force("charge", d3.forceManyBody().strength(-50).distanceMax(270))
.force("center", d3.forceCenter())
}
Insert cell
dataset = d3.json("https://gist.githubusercontent.com/emanueles/7b7723386677bb13763208216fd89c1f/raw/d09478158ba0fe8aa616deee8bcfe908bba17f15/songs.json")
Insert cell
Insert cell
html`Esta célula contém os estilos da visualização.
<style>
line.link {
fill: none;
stroke: #ddd;
stroke-opacity: 0.8;
stroke-width: 1.5px;
}
<style>`
Insert cell
d3 = require('d3')
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more