Public
Edited
Jun 8
Insert cell
Insert cell
Insert cell
// Importación de Datos
mc1_graph1 = FileAttachment("MC1_graph.json").json()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
graph = FileAttachment("MC1_graph.json").json()
Insert cell
songsFolk = graph.nodes.filter(d =>
(d["Node Type"] === "Song" || d["Node Type"] === "Album") &&
d.genre === "Oceanus Folk"
)
Insert cell
Insert cell
tiposDeInfluencia = [
"InStyleOf",
"CoverOf",
"LyricalReferenceTo",
"InterpolatesFrom",
"DirectlySamples"
]
Insert cell
folkIDs = new Set(songsFolk.map(d => d.id)) // ahora es Set<number>

Insert cell
influenciasSalientes = graph.links.filter(d =>
folkIDs.has(d.source) && tiposDeInfluencia.includes(d["Edge Type"])
)

Insert cell
influenciasSalientes.length
Insert cell
/* En los datos, hay canciones de Oceanus Folk que son influenciadas por otras canciones de Oceanus Folk. Es decir, Oceanus Folk se está auto-influenciando, lo cual puede pasar si hay obras etiquetadas como Oceanus Folk que influencian a otras también etiquetadas como Oceanus Folk. En el grafo hay ciclos o relaciones internas dentro del mismo género.
Por este motivo, se agregó un filtro para filtrar solo influencias a otros géneros. */

influenciados1 = influenciasSalientes
.map(d => {
const nodoOrigen = graph.nodes.find(n => n.id === d.source);
const nodoDestino = graph.nodes.find(n => n.id === d.target);

if (!nodoOrigen || !nodoDestino) return null;

// Solo si el origen es Oceanus Folk y el destino NO es del mismo género
if (nodoOrigen.genre === "Oceanus Folk" && nodoDestino.genre !== "Oceanus Folk") {
return {
id: d.target,
genre: nodoDestino.genre,
year: nodoDestino.release_date || nodoDestino.written_date || "Desconocido"
};
}

return null;
})
.filter(d => d && d.genre && d.year);
Insert cell
Insert cell
// Visualización: Influencia de Oceanus Folk
Plot.plot({
marginLeft: 140,
height: 500,
y: {label: "Año", type: "band"},
x: {
label: "Cantidad de obras influenciadas",
grid: true
},
style: {
fontFamily: "sans-serif",
fontSize: "12px"
},
color: {
type: "linear",
scheme: "blues",
label: "Cantidad",
legend: true
},
marks: [
Plot.barX(
d3.rollups(influenciados1, v => v.length, d => d.year)
.map(function(d) { return {year: d[0], count: d[1]}; })
.sort(function(a, b) { return a.year - b.year; }),
{
x: "count",
y: "year",
fill: "count"
}
),
Plot.text(
d3.rollups(influenciados1, v => v.length, d => d.year)
.map(function(d) { return {year: d[0], count: d[1]}; })
.sort(function(a, b) { return a.year - b.year; }),
{
x: function(d) { return d.count + 2; },
y: "year",
text: function(d) { return d.count; },
fill: "black",
textAnchor: "start",
fontSize: 10
}
)
]
})

Insert cell
// Visualización: Influencia de Oceanus Folk
Plot.plot({
width: 700,
height: 400,
x: {label: "Año", type: "linear"},
y: {label: "Cantidad de obras influenciadas"},
marks: [
Plot.line(
d3.rollups(influenciados1, v => v.length, d => +d.year)
.map(d => ({year: d[0], count: d[1]}))
.sort((a, b) => a.year - b.year),
{x: "year", y: "count", stroke: "#5e60ce"}
),
Plot.dot(
d3.rollups(influenciados1, v => v.length, d => +d.year)
.map(d => ({year: d[0], count: d[1]}))
.sort((a, b) => a.year - b.year),
{x: "year", y: "count", fill: "#5e60ce"}
),
Plot.text(
d3.rollups(influenciados1, v => v.length, d => +d.year)
.map(d => ({year: d[0], count: d[1]}))
.sort((a, b) => a.year - b.year),
{
x: "year",
y: d => d.count,
text: d => d.count,
dy: -8,
fontSize: 10
}
)
]
});

Insert cell
Plot.barX(
d3.rollups(influenciados1, v => v.length, d => d.year)
.map(function(d) { return {year: d[0], count: d[1]}; })
.sort(function(a, b) { return a.year - b.year; }),
{
x: "count",
y: "year",
fill: "count"
}
)

Insert cell
Insert cell
Insert cell
// Visualización: Géneros más influenciados por Oceanus Folk
Plot.plot({
marginLeft: 140,
x: {label: "Cantidad"},
y: {label: "Género", type: "band"},
marks: [
// Barras horizontales
Plot.barX(
d3.rollups(influenciados1, v => v.length, d => d.genre)
.map(([genre, count]) => ({genre, count}))
.sort((a, b) => d3.descending(a.count, b.count)),
{x: "count", y: "genre"}
),
// Etiquetas con el número
Plot.text(
d3.rollups(influenciados1, v => v.length, d => d.genre)
.map(([genre, count]) => ({genre, count}))
.sort((a, b) => d3.descending(a.count, b.count)),
{
x: d => d.count + 1, // posición a la derecha de la barra
y: "genre",
text: d => d.count,
fill: "black",
textAnchor: "start",
fontSize: 10
}
)
]
});

Insert cell
// Datos de géneros más influenciados, en base a histograma precedente
genresTreemapData = [
{ genre: "Indie Folk", count: 100 },
{ genre: "Dream Pop", count: 15 },
{ genre: "Synthwave", count: 19 },
{ genre: "Doom Metal", count: 14 },
{ genre: "Desert Rock", count: 9 },
{ genre: "Jazz Surf Rock", count: 1 },
{ genre: "Emo/Pop Punk", count: 1 },
{ genre: "Celtic Folk", count: 1 },
{ genre: "Southern Gothic Rock", count: 4 }
]

Insert cell
// Visualización: Géneros más influenciados por Oceanus Folk
{
const width = 1000;
const height = 400;

const root = d3.hierarchy({children: genresTreemapData})
.sum(d => d.count);

d3.treemap()
.size([width, height])
.padding(2)(root);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

const color = d3.scaleOrdinal(d3.schemeTableau10);

const node = svg.selectAll("g")
.data(root.leaves())
.join("g")
.attr("transform", d => `translate(${d.x0},${d.y0})`);

node.append("rect")
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0)
.attr("fill", d => color(d.data.genre));

node.append("text")
.attr("x", 4)
.attr("y", 14)
.text(d => `${d.data.genre} (${d.data.count})`)
.attr("fill", "white")
.style("font-size", "12px");

return svg.node();
}
Insert cell
Insert cell
// Paso 1: IDs de obras influenciadas
obrasInfluenciadas = influenciasSalientes.map(d => d.target)
Insert cell
// Paso 2: Buscamos relaciones PerformerOf sobre esas obras
performances = graph.links.filter(d =>
obrasInfluenciadas.includes(d.target) && d["Edge Type"] === "PerformerOf"
)
Insert cell
// Paso 3: Mapeamos performers únicos
performers = performances.map(d => {
const persona = graph.nodes.find(n => n.id === d.source && n["Node Type"] === "Person")
return persona?.stage_name || persona?.name
}).filter(Boolean)
Insert cell
// Paso 4: Contamos cuántas veces aparece cada artista
conteoArtistas = d3.rollups(
performers,
v => v.length,
d => d
).map(([name, count]) => ({name, count}))
.sort((a, b) => d3.descending(a.count, b.count))
Insert cell
// Visualización: Artistas destacados más influenciados por Oceanus Folk
Plot.plot({
marginLeft: 160,
x: {label: "Cantidad de obras influenciadas"},
y: {label: "Artista", type: "band"},
color: {
type: "categorical",
scheme: "set2", // Alternativas: "set3", "tableau10", "dark2", etc.
legend: false // Ocultamos la leyenda si no la necesitás
},
marks: [
Plot.barX(conteoArtistas.slice(0, 10), {
x: "count",
y: "name",
fill: "name" // Colorea cada artista distinto
}),
Plot.text(conteoArtistas.slice(0, 10), {
x: d => d.count + 0.2,
y: "name",
text: d => d.count,
textAnchor: "start",
fontSize: 10,
fill: "black"
})
]
});

Insert cell
Insert cell
Insert cell
// Paso 1: Filtramos obras de Oceanus Folk posteriores a 2028
folkPostSailor = songsFolk.filter(d => +d.release_date >= 2028)
Insert cell
// Paso 1.b : Obtenemos los IDs
folkPostIDs = new Set(folkPostSailor.map(d => d.id))
Insert cell
// Paso 2: Buscamos influencias entrantes sobre esas obras
influenciasEntrantes = graph.links.filter(d =>
folkPostIDs.has(d.target) &&
tiposDeInfluencia.includes(d["Edge Type"])
)
Insert cell
// Paso 3: Extraemos géneros de los nodos que las influenciaron
origenes2 = influenciasEntrantes.map(d => {
const nodoOrigen = graph.nodes.find(n => n.id === d.source);
return {
id: d.source,
genre: nodoOrigen?.genre ?? "Desconocido",
year: nodoOrigen?.release_date || nodoOrigen?.written_date || "Desconocido"
};
}).filter(d =>
d.genre &&
d.year &&
d.genre !== "Oceanus Folk" // Excluimos auto-influencia
);
Insert cell
origenesTreemapData = d3.rollups(origenes2, v => v.length, d => d.genre)
.map(([genre, count]) => ({ genre, count }))
.sort((a, b) => d3.descending(a.count, b.count));
Insert cell
// Visualización: Géneros de los que Oceanus Folk extrae la mayor parte de su inspiración contemporánea
{
const data = origenesTreemapData;

const root = d3.pack()
.size([600, 600])
.padding(3)(
d3.hierarchy({children: data}).sum(d => d.count)
);

const color = d3.scaleOrdinal(d3.schemeSet2);

const svg = d3.create("svg")
.attr("width", 600)
.attr("height", 600);

const node = svg.selectAll("g")
.data(root.leaves())
.join("g")
.attr("transform", d => `translate(${d.x},${d.y})`);

node.append("circle")
.attr("r", d => d.r)
.attr("fill", d => color(d.data.genre))
.attr("stroke", "#333");

node.append("text")
.text(d => d.data.genre)
.attr("text-anchor", "middle")
.attr("dy", "0.3em")
.style("font-size", "10px")
.style("fill", "white");

return svg.node();
}

Insert cell
Insert cell
Insert cell
// Top 20 artistas según suma de éxitos de canciones y álbumes
{
const songCounts = d3.rollup(
songsWithArtistIds.filter(s => s.notable).flatMap(s => s.artistIds),
v => v.length,
id => id
);
const albumCounts = d3.rollup(
albumsWithArtistIds.filter(a => a.notable).flatMap(a => a.artistIds),
v => v.length,
id => id
);
const artists = Array.from(artistNodesById.values()).map(node => {
const songs = songCounts.get(node.id) || 0;
const albums = albumCounts.get(node.id) || 0;
return {
name: node.name,
song_successes: songs,
album_successes: albums,
total_successes: songs + albums
};
});
const top20 = artists
.filter(d => d.total_successes > 0)
.sort((a, b) => d3.descending(a.total_successes, b.total_successes))
.slice(0, 20);
return md`
| Artista | Canciones exitosas | Álbumes exitosos | Total éxitos |
|---------|--------------------|------------------|--------------|
${top20.map(d => `| ${d.name} | ${d.song_successes} | ${d.album_successes} | ${d.total_successes} |`).join("\n")}
`;
}

Insert cell
Insert cell
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