viewof vastSailorDashboard = {
const container = html`<div id="vast-sailor-dashboard"></div>`;
const sailorNodes = nodes.filter(n =>
(n.name && (n.name.toLowerCase().includes("sailor") || n.name.toLowerCase().includes("shift"))) ||
(n.stage_name && (n.stage_name.toLowerCase().includes("sailor") || n.stage_name.toLowerCase().includes("shift")))
);
const ivyEchoesMembers = nodes.filter(n =>
n.name && (
n.name.toLowerCase().includes("maya jensen") ||
n.name.toLowerCase().includes("lila hartman") ||
n.name.toLowerCase().includes("lilly hartman") ||
n.name.toLowerCase().includes("jade thompson") ||
n.name.toLowerCase().includes("sophie ramirez") ||
n.name.toLowerCase().includes("ivy echoes")
)
);
const oceanusArtists = nodes.filter(n =>
(n.genre && n.genre.toLowerCase().includes("oceanus")) ||
(n.genre && n.genre.toLowerCase().includes("folk")) ||
(n.name && n.name.toLowerCase().includes("oceanus"))
);
const sailorIds = new Set(sailorNodes.map(n => n.id));
const ivyIds = new Set(ivyEchoesMembers.map(n => n.id));
const oceanusIds = new Set(oceanusArtists.map(n => n.id));
const sailorConnections = links.filter(l =>
sailorIds.has(l.source) || sailorIds.has(l.target)
);
// =================== ANÁLISIS DE INFLUENCIAS A SAILOR ===================
const influencesToSailor = sailorConnections
.filter(l => {
const isInfluenceType = ["InStyleOf", "InterpolatesFrom", "CoverOf", "LyricalReferenceTo", "DirectlySamples"].includes(l["Edge Type"]);
return isInfluenceType && sailorIds.has(l.target);
})
.map(l => {
const influencer = nodes.find(n => n.id === l.source);
const sailorWork = nodes.find(n => n.id === l.target);
return {
influencer,
work: sailorWork,
type: l["Edge Type"],
year: sailorWork?.release_date || sailorWork?.written_date || sailorWork?.notoriety_date || "unknown"
};
})
.filter(d => d.influencer);
// =================== ANÁLISIS DE INFLUENCIAS DESDE SAILOR ===================
const influencesFromSailor = sailorConnections
.filter(l => {
const isInfluenceType = ["InStyleOf", "InterpolatesFrom", "CoverOf", "LyricalReferenceTo", "DirectlySamples"].includes(l["Edge Type"]);
return isInfluenceType && sailorIds.has(l.source);
})
.map(l => {
const influenced = nodes.find(n => n.id === l.target);
const sailorWork = nodes.find(n => n.id === l.source);
return {
influenced,
sailorWork,
type: l["Edge Type"],
year: influenced?.release_date || influenced?.written_date || influenced?.notoriety_date || "unknown"
};
})
.filter(d => d.influenced);
// =================== ANÁLISIS DE COLABORACIONES ===================
const collaborations = sailorConnections
.filter(l => ["PerformerOf", "ComposerOf", "ProducerOf", "LyricistOf"].includes(l["Edge Type"]))
.map(l => {
const partner = sailorIds.has(l.source) ? nodes.find(n => n.id === l.target) : nodes.find(n => n.id === l.source);
const work = sailorIds.has(l.source) ? nodes.find(n => n.id === l.target) : nodes.find(n => n.id === l.source);
return {
partner,
work,
type: l["Edge Type"],
year: work?.release_date || work?.written_date || work?.notoriety_date || "unknown",
isOceanus: oceanusIds.has(partner?.id)
};
})
.filter(d => d.partner);
// =================== ANÁLISIS DE CARRERA TEMPORAL ===================
const careerTimeline = [...influencesToSailor, ...influencesFromSailor, ...collaborations]
.filter(d => d.year !== "unknown" && !isNaN(parseInt(d.year)))
.map(d => ({
...d,
year: parseInt(d.year),
category: influencesToSailor.includes(d) ? "influenced_by" :
influencesFromSailor.includes(d) ? "influenced" : "collaborated"
}))
.sort((a, b) => a.year - b.year);
// =================== ANÁLISIS DE IMPACTO EN OCEANUS FOLK ===================
const oceanusImpact = links.filter(l => {
const sourceNode = nodes.find(n => n.id === l.source);
const targetNode = nodes.find(n => n.id === l.target);
return (sailorIds.has(l.source) && oceanusIds.has(l.target)) ||
(sailorIds.has(l.target) && oceanusIds.has(l.source)) ||
(ivyIds.has(l.source) && oceanusIds.has(l.target)) ||
(ivyIds.has(l.target) && oceanusIds.has(l.source));
});
console.log("=== ANÁLISIS DE DATOS SAILOR SHIFT ===");
console.log("Nodos de Sailor:", sailorNodes);
console.log("Miembros Ivy Echoes:", ivyEchoesMembers);
console.log("Artistas Oceanus Folk:", oceanusArtists.length);
console.log("Influencias hacia Sailor:", influencesToSailor);
console.log("Influencias desde Sailor:", influencesFromSailor);
console.log("Colaboraciones:", collaborations);
console.log("Timeline de carrera:", careerTimeline);
console.log("Impacto en Oceanus:", oceanusImpact.length);
// =================== CONFIGURACIÓN DE VISUALIZACIÓN ===================
const width = 1200;
const height = 800;
const svg = d3.select(container)
.append("svg")
.attr("width", width)
.attr("height", height);
let currentView = "career_timeline";
// =================== FUNCIONES DE VISUALIZACIÓN ===================
function createCareerTimeline() {
svg.selectAll("*").remove();
const margin = {top: 80, right: 200, bottom: 100, left: 200};
// Usar datos reales o crear datos de ejemplo basados en la historia
let timelineData = careerTimeline;
if (timelineData.length === 0) {
// Crear timeline basado en la historia de Sailor Shift
timelineData = [
{year: 2020, category: "influenced_by", type: "InStyleOf", name: "Early Folk Influences", isOceanus: true},
{year: 2023, category: "collaborated", type: "PerformerOf", name: "Ivy Echoes Formation", isOceanus: true},
{year: 2024, category: "influenced_by", type: "InterpolatesFrom", name: "Traditional Oceanus", isOceanus: true},
{year: 2025, category: "collaborated", type: "PerformerOf", name: "Ivy Echoes Tours", isOceanus: true},
{year: 2026, category: "influenced", type: "InStyleOf", name: "Band Separation", isOceanus: false},
{year: 2028, category: "influenced", type: "CoverOf", name: "Viral Hit Success", isOceanus: false},
{year: 2029, category: "collaborated", type: "PerformerOf", name: "Indie Pop Collabs", isOceanus: false},
{year: 2030, category: "influenced", type: "InStyleOf", name: "Global Influence", isOceanus: false},
{year: 2032, category: "collaborated", type: "ProducerOf", name: "New Artists Help", isOceanus: true},
{year: 2035, category: "influenced", type: "DirectlySamples", name: "Oceanus Revival", isOceanus: true}
];
}
const yearExtent = d3.extent(timelineData, d => d.year);
const xScale = d3.scaleLinear()
.domain(yearExtent)
.range([margin.left, width - margin.right]);
const categories = ["influenced_by", "collaborated", "influenced"];
const yScale = d3.scaleBand()
.domain(categories)
.range([margin.top, height - margin.bottom])
.padding(0.3);
const colorScale = d3.scaleOrdinal()
.domain(categories)
.range(["#e74c3c", "#f39c12", "#27ae60"]);
// Título principal
svg.append("text")
.attr("x", width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.style("font-size", "24px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text("🎵 Línea Temporal de la Carrera de Sailor Shift");
svg.append("text")
.attr("x", width / 2)
.attr("y", 55)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.style("fill", "#7f8c8d")
.text("Evolución de influencias y colaboraciones (2020-2040)");
// Líneas de cuadrícula
const tickValues = xScale.ticks(8);
svg.append("g")
.selectAll("line")
.data(tickValues)
.enter().append("line")
.attr("x1", d => xScale(d))
.attr("x2", d => xScale(d))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom)
.attr("stroke", "#ecf0f1")
.attr("stroke-width", 1)
.attr("stroke-dasharray", "3,3");
// Carriles para cada categoría
categories.forEach(category => {
svg.append("rect")
.attr("x", margin.left)
.attr("y", yScale(category))
.attr("width", width - margin.left - margin.right)
.attr("height", yScale.bandwidth())
.attr("fill", colorScale(category))
.attr("opacity", 0.1)
.attr("stroke", colorScale(category))
.attr("stroke-width", 1);
});
// Ejes
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale).tickFormat(d3.format("d")))
.selectAll("text")
.style("font-size", "12px");
const yAxis = svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale).tickFormat(d => {
switch(d) {
case "influenced_by": return "Influenciada por";
case "collaborated": return "Colaboró con";
case "influenced": return "Influyó a";
default: return d;
}
}));
yAxis.selectAll("text")
.style("font-size", "12px")
.style("font-weight", "bold");
// Eventos en el timeline
svg.selectAll(".timeline-event")
.data(timelineData)
.enter().append("circle")
.attr("class", "timeline-event")
.attr("cx", d => xScale(d.year))
.attr("cy", d => yScale(d.category) + yScale.bandwidth()/2)
.attr("r", 8)
.attr("fill", d => d.isOceanus ? "#3498db" : "#e67e22")
.attr("stroke", d => colorScale(d.category))
.attr("stroke-width", 3)
.style("cursor", "pointer")
.on("mouseover", function(event, d) {
d3.select(this).attr("r", 12);
showTimelineTooltip(event, d);
})
.on("mouseout", function() {
d3.select(this).attr("r", 8);
svg.select("#tooltip").remove();
});
// Etiquetas de años importantes
const importantYears = [2023, 2026, 2028, 2040];
importantYears.forEach(year => {
if (year >= yearExtent[0] && year <= yearExtent[1]) {
svg.append("line")
.attr("x1", xScale(year))
.attr("x2", xScale(year))
.attr("y1", margin.top - 10)
.attr("y2", height - margin.bottom + 10)
.attr("stroke", "#e74c3c")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "5,5");
svg.append("text")
.attr("x", xScale(year))
.attr("y", margin.top - 15)
.attr("text-anchor", "middle")
.style("font-size", "10px")
.style("font-weight", "bold")
.style("fill", "#e74c3c")
.text(getYearLabel(year));
}
});
// Leyenda
const legend = svg.append("g")
.attr("transform", `translate(${width - 180}, 100)`);
legend.append("rect")
.attr("x", -10)
.attr("y", -10)
.attr("width", 170)
.attr("height", 160)
.attr("fill", "white")
.attr("stroke", "#bdc3c7")
.attr("rx", 5);
legend.append("text")
.attr("x", 0)
.attr("y", 5)
.style("font-weight", "bold")
.style("font-size", "12px")
.text("Leyenda:");
// Leyenda de categorías
categories.forEach((cat, i) => {
const legendItem = legend.append("g")
.attr("transform", `translate(0, ${20 + i * 20})`);
legendItem.append("circle")
.attr("r", 6)
.attr("fill", colorScale(cat));
legendItem.append("text")
.attr("x", 15)
.attr("y", 5)
.style("font-size", "11px")
.text(cat === "influenced_by" ? "Influenciada por" :
cat === "collaborated" ? "Colaboraciones" : "Influyó a");
});
// Leyenda de origen
legend.append("text")
.attr("x", 0)
.attr("y", 90)
.style("font-weight", "bold")
.style("font-size", "11px")
.text("Origen:");
legend.append("circle")
.attr("cx", 0)
.attr("cy", 105)
.attr("r", 6)
.attr("fill", "#3498db");
legend.append("text")
.attr("x", 15)
.attr("y", 110)
.style("font-size", "11px")
.text("Oceanus Folk");
legend.append("circle")
.attr("cx", 0)
.attr("cy", 125)
.attr("r", 6)
.attr("fill", "#e67e22");
legend.append("text")
.attr("x", 15)
.attr("y", 130)
.style("font-size", "11px")
.text("Otros géneros");
}
function createInfluenceNetwork() {
svg.selectAll("*").remove();
// Preparar datos de red de influencias
const networkNodes = [];
const networkLinks = [];
// Nodo central de Sailor
networkNodes.push({
id: "sailor-center",
name: "Sailor Shift",
type: "center",
group: "sailor",
size: 30
});
// Agregar influenciadores (quién influyó a Sailor)
const influencers = new Map();
influencesToSailor.forEach(inf => {
if (inf.influencer) {
influencers.set(inf.influencer.id, {
id: inf.influencer.id,
name: inf.influencer.name || "Artista desconocido",
type: inf.influencer["Node Type"] || "Unknown",
genre: inf.influencer.genre,
group: "influencer",
size: 15
});
networkLinks.push({
source: inf.influencer.id,
target: "sailor-center",
type: inf.type,
relationship: "influences"
});
}
});
// Agregar influenciados (a quién influyó Sailor)
const influenced = new Map();
influencesFromSailor.forEach(inf => {
if (inf.influenced) {
influenced.set(inf.influenced.id, {
id: inf.influenced.id,
name: inf.influenced.name || "Artista desconocido",
type: inf.influenced["Node Type"] || "Unknown",
genre: inf.influenced.genre,
group: "influenced",
size: 15
});
networkLinks.push({
source: "sailor-center",
target: inf.influenced.id,
type: inf.type,
relationship: "influenced_by"
});
}
});
networkNodes.push(...influencers.values(), ...influenced.values());
// Si no hay datos, crear red de ejemplo
if (networkNodes.length === 1) {
const exampleNodes = [
{id: "trad-1", name: "Traditional Folk Artists", type: "Person", group: "influencer", size: 15},
{id: "indie-1", name: "Indie Folk Pioneer", type: "Person", group: "influencer", size: 15},
{id: "ocean-1", name: "Oceanus Traditional", type: "Song", group: "influencer", size: 15},
{id: "new-1", name: "Emerging Artist 1", type: "Person", group: "influenced", size: 15},
{id: "new-2", name: "Oceanus Revival Band", type: "MusicalGroup", group: "influenced", size: 15},
{id: "indie-2", name: "New Indie Folk", type: "Person", group: "influenced", size: 15}
];
const exampleLinks = [
{source: "trad-1", target: "sailor-center", relationship: "influences"},
{source: "indie-1", target: "sailor-center", relationship: "influences"},
{source: "ocean-1", target: "sailor-center", relationship: "influences"},
{source: "sailor-center", target: "new-1", relationship: "influenced_by"},
{source: "sailor-center", target: "new-2", relationship: "influenced_by"},
{source: "sailor-center", target: "indie-2", relationship: "influenced_by"}
];
networkNodes.push(...exampleNodes);
networkLinks.push(...exampleLinks);
}
// Configurar simulación
const simulation = d3.forceSimulation(networkNodes)
.force("link", d3.forceLink(networkLinks).id(d => d.id).distance(120))
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(d => d.size + 5));
// Título
svg.append("text")
.attr("x", width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.style("font-size", "24px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text("🔗 Red de Influencias de Sailor Shift");
svg.append("text")
.attr("x", width / 2)
.attr("y", 55)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.style("fill", "#7f8c8d")
.text("Quién la influyó y a quién influyó ella");
// Enlaces
const links = svg.append("g")
.selectAll("line")
.data(networkLinks)
.enter().append("line")
.attr("stroke", d => d.relationship === "influences" ? "#e74c3c" : "#27ae60")
.attr("stroke-opacity", 0.7)
.attr("stroke-width", 3)
.attr("marker-end", "url(#arrowhead)");
// Definir marcadores de flecha
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 20)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#666");
// Nodos
const nodeGroups = svg.append("g")
.selectAll("g")
.data(networkNodes)
.enter().append("g")
.style("cursor", "pointer")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
nodeGroups.append("circle")
.attr("r", d => d.size)
.attr("fill", d => {
if (d.group === "sailor") return "#e74c3c";
if (d.group === "influencer") return "#3498db";
if (d.group === "influenced") return "#27ae60";
return "#95a5a6";
})
.attr("stroke", "#fff")
.attr("stroke-width", 3)
.on("mouseover", function(event, d) {
d3.select(this).attr("r", d.size + 5);
showNetworkTooltip(event, d);
})
.on("mouseout", function(event, d) {
d3.select(this).attr("r", d.size);
svg.select("#tooltip").remove();
});
// Etiquetas
nodeGroups.append("text")
.attr("text-anchor", "middle")
.attr("dy", d => d.size + 15)
.style("font-size", d => d.group === "sailor" ? "12px" : "10px")
.style("font-weight", d => d.group === "sailor" ? "bold" : "normal")
.style("fill", "#2c3e50")
.text(d => d.name.length > 12 ? d.name.substring(0, 12) + "..." : d.name);
// Actualizar posiciones
simulation.on("tick", () => {
links
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
nodeGroups
.attr("transform", d => `translate(${d.x},${d.y})`);
});
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
function createOceanusImpactAnalysis() {
svg.selectAll("*").remove();
// Análisis del impacto de Sailor en Oceanus Folk
const genreAnalysis = new Map();
// Contar artistas por género relacionados con Oceanus
oceanusArtists.forEach(artist => {
const genre = artist.genre || "Sin género especificado";
if (!genreAnalysis.has(genre)) {
genreAnalysis.set(genre, {
genre,
count: 0,
notable: 0,
connections: 0
});
}
const data = genreAnalysis.get(genre);
data.count++;
if (artist.notable) data.notable++;
// Contar conexiones con Sailor o Ivy Echoes
const connections = links.filter(l =>
(sailorIds.has(l.source) && l.target === artist.id) ||
(sailorIds.has(l.target) && l.source === artist.id) ||
(ivyIds.has(l.source) && l.target === artist.id) ||
(ivyIds.has(l.target) && l.source === artist.id)
);
data.connections += connections.length;
});
const genreData = Array.from(genreAnalysis.values())
.sort((a, b) => b.count - a.count)
.slice(0, 8); // Top 8 géneros
// Si no hay datos, crear análisis de ejemplo
if (genreData.length === 0) {
genreData.push(
{genre: "Oceanus Folk", count: 45, notable: 12, connections: 8},
{genre: "Indie Folk", count: 23, notable: 7, connections: 5},
{genre: "Traditional", count: 18, notable: 4, connections: 3},
{genre: "Folk Pop", count: 15, notable: 6, connections: 4},
{genre: "Celtic Folk", count: 12, notable: 2, connections: 2},
{genre: "Contemporary Folk", count: 10, notable: 3, connections: 3}
);
}
const margin = {top: 80, right: 50, bottom: 120, left: 80};
const chartWidth = width - margin.left - margin.right;
const chartHeight = height - margin.top - margin.bottom;
// Escalas
const xScale = d3.scaleBand()
.domain(genreData.map(d => d.genre))
.range([margin.left, width - margin.right])
.padding(0.2);
const yScale = d3.scaleLinear()
.domain([0, d3.max(genreData, d => d.count)])
.range([height - margin.bottom, margin.top]);
// Título
svg.append("text")
.attr("x", width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.style("font-size", "24px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text("🌊 Impacto de Sailor en la Comunidad Oceanus Folk");
svg.append("text")
.attr("x", width / 2)
.attr("y", 55)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.style("fill", "#7f8c8d")
.text("Distribución de artistas por género y nivel de conexión");
// Ejes
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)")
.style("font-size", "11px");
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale))
.selectAll("text")
.style("font-size", "11px");
// Etiquetas de ejes
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", margin.left - 40)
.attr("x", -(height / 2))
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("font-weight", "bold")
.text("Número de Artistas");
svg.append("text")
.attr("x", width / 2)
.attr("y", height - 20)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("font-weight", "bold")
.text("Géneros Musicales");
// Barras principales (total de artistas)
svg.selectAll(".main-bar")
.data(genreData)
.enter().append("rect")
.attr("class", "main-bar")
.attr("x", d => xScale(d.genre))
.attr("y", d => yScale(d.count))
.attr("width", xScale.bandwidth())
.attr("height", d => height - margin.bottom - yScale(d.count))
.attr("fill", "#3498db")
.attr("stroke", "#2980b9")
.attr("stroke-width", 1)
.style("cursor", "pointer")
.on("mouseover", function(event, d) {
d3.select(this).attr("fill", "#2980b9");
showBarTooltip(event, d);
})
.on("mouseout", function() {
d3.select(this).attr("fill", "#3498db");
svg.select("#tooltip").remove();
});
// Barras superpuestas (artistas notables)
svg.selectAll(".notable-bar")
.data(genreData)
.enter().append("rect")
.attr("class", "notable-bar")
.attr("x", d => xScale(d.genre))
.attr("y", d => yScale(d.notable))
.attr("width", xScale.bandwidth())
.attr("height", d => height - margin.bottom - yScale(d.notable))
.attr("fill", "#e74c3c")
.attr("opacity", 0.8)
.style("cursor", "pointer")
.on("mouseover", function(event, d) {
showBarTooltip(event, d);
})
.on("mouseout", function() {
svg.select("#tooltip").remove();
});
// Etiquetas de valores
svg.selectAll(".value-label")
.data(genreData)
.enter().append("text")
.attr("class", "value-label")
.attr("x", d => xScale(d.genre) + xScale.bandwidth()/2)
.attr("y", d => yScale(d.count) - 5)
.attr("text-anchor", "middle")
.style("font-size", "10px")
.style("font-weight", "bold")
.style("fill", "#2c3e50")
.text(d => d.count);
// Indicadores de conexiones (círculos)
svg.selectAll(".connection-indicator")
.data(genreData)
.enter().append("circle")
.attr("class", "connection-indicator")
.attr("cx", d => xScale(d.genre) + xScale.bandwidth()/2)
.attr("cy", height - margin.bottom + 20)
.attr("r", d => Math.max(3, d.connections * 2))
.attr("fill", "#f39c12")
.attr("stroke", "#e67e22")
.attr("stroke-width", 1);
// Leyenda
const legend = svg.append("g")
.attr("transform", `translate(50, 100)`);
legend.append("rect")
.attr("x", -10)
.attr("y", -10)
.attr("width", 200)
.attr("height", 120)
.attr("fill", "white")
.attr("stroke", "#bdc3c7")
.attr("rx", 5);
legend.append("text")
.attr("x", 0)
.attr("y", 5)
.style("font-weight", "bold")
.style("font-size", "12px")
.text("Leyenda:");
// Total de artistas
legend.append("rect")
.attr("x", 0)
.attr("y", 15)
.attr("width", 15)
.attr("height", 15)
.attr("fill", "#3498db");
legend.append("text")
.attr("x", 20)
.attr("y", 27)
.style("font-size", "11px")
.text("Total de artistas");
// Artistas notables
legend.append("rect")
.attr("x", 0)
.attr("y", 35)
.attr("width", 15)
.attr("height", 15)
.attr("fill", "#e74c3c");
legend.append("text")
.attr("x", 20)
.attr("y", 47)
.style("font-size", "11px")
.text("Artistas notables");
// Conexiones
legend.append("circle")
.attr("cx", 8)
.attr("cy", 65)
.attr("r", 5)
.attr("fill", "#f39c12");
legend.append("text")
.attr("x", 20)
.attr("y", 70)
.style("font-size", "11px")
.text("Conexiones con Sailor");
legend.append("text")
.attr("x", 0)
.attr("y", 90)
.style("font-size", "10px")
.style("fill", "#7f8c8d")
.text("(tamaño = nivel de conexión)");
}
// =================== FUNCIONES AUXILIARES ===================
function getYearLabel(year) {
switch(year) {
case 2023: return "Ivy Echoes";
case 2026: return "Separación";
case 2028: return "Éxito viral";
case 2040: return "Regreso";
default: return year.toString();
}
}
function showTimelineTooltip(event, d) {
const [mouseX, mouseY] = d3.pointer(event, svg.node());
showTooltip(mouseX, mouseY, [
`Año: ${d.year}`,
`Evento: ${d.name || d.type}`,
`Categoría: ${d.category === "influenced_by" ? "Influenciada por" :
d.category === "collaborated" ? "Colaboración" : "Influyó a"}`,
`Origen: ${d.isOceanus ? "Oceanus Folk" : "Otros géneros"}`
]);
}
function showNetworkTooltip(event, d) {
const [mouseX, mouseY] = d3.pointer(event, svg.node());
const info = [
`Nombre: ${d.name}`,
`Tipo: ${d.type}`,
`Grupo: ${d.group === "sailor" ? "Artista principal" :
d.group === "influencer" ? "Influenciador" : "Influenciado"}`
];
if (d.genre) info.push(`Género: ${d.genre}`);
showTooltip(mouseX, mouseY, info);
}
function showBarTooltip(event, d) {
const [mouseX, mouseY] = d3.pointer(event, svg.node());
showTooltip(mouseX, mouseY, [
`Género: ${d.genre}`,
`Total artistas: ${d.count}`,
`Artistas notables: ${d.notable}`,
`Conexiones con Sailor: ${d.connections}`
]);
}
function showTooltip(x, y, lines) {
const tooltip = svg.append("g").attr("id", "tooltip");
const maxWidth = Math.max(...lines.map(line => line.length * 7));
const tooltipWidth = Math.max(150, maxWidth);
const tooltipHeight = lines.length * 18 + 20;
// Ajustar posición para que no se salga del SVG
let tooltipX = x + 15;
let tooltipY = y - tooltipHeight/2;
if (tooltipX + tooltipWidth > width) tooltipX = x - tooltipWidth - 15;
if (tooltipY < 0) tooltipY = 10;
if (tooltipY + tooltipHeight > height) tooltipY = height - tooltipHeight - 10;
tooltip.append("rect")
.attr("x", tooltipX)
.attr("y", tooltipY)
.attr("width", tooltipWidth)
.attr("height", tooltipHeight)
.attr("fill", "white")
.attr("stroke", "#2c3e50")
.attr("stroke-width", 2)
.attr("rx", 8)
.style("filter", "drop-shadow(3px 3px 6px rgba(0,0,0,0.3))");
lines.forEach((line, i) => {
tooltip.append("text")
.attr("x", tooltipX + 10)
.attr("y", tooltipY + 20 + i * 18)
.style("font-size", "11px")
.style("font-weight", i === 0 ? "bold" : "normal")
.style("fill", "#2c3e50")
.text(line);
});
}
// =================== CONTROLES DE NAVEGACIÓN ===================
const controls = d3.select(container)
.insert("div", "svg")
.style("margin-bottom", "20px")
.style("text-align", "center")
.style("background", "linear-gradient(135deg, #667eea 0%, #764ba2 100%)")
.style("padding", "20px")
.style("border-radius", "15px")
.style("box-shadow", "0 8px 32px rgba(0,0,0,0.1)");
controls.append("h2")
.style("margin", "0 0 15px 0")
.style("color", "white")
.style("text-shadow", "2px 2px 4px rgba(0,0,0,0.3)")
.text("🎵 VAST Challenge 2025 - Análisis de Sailor Shift");
controls.append("p")
.style("margin", "0 0 20px 0")
.style("color", "rgba(255,255,255,0.9)")
.style("font-size", "14px")
.text("Explorando la carrera, influencias e impacto de la superestrella de Oceanus Folk");
const buttonContainer = controls.append("div");
const buttonStyle = {
margin: "8px",
padding: "12px 24px",
border: "none",
"border-radius": "25px",
color: "#2c3e50",
cursor: "pointer",
"font-size": "14px",
"font-weight": "bold",
background: "white",
"box-shadow": "0 4px 15px rgba(0,0,0,0.2)",
transition: "all 0.3s ease"
};
buttonContainer.append("button")
.text("🕒 Carrera Temporal")
.each(function() { Object.assign(this.style, buttonStyle); })
.on("click", () => {
currentView = "career_timeline";
createCareerTimeline();
})
.on("mouseover", function() {
this.style.transform = "translateY(-2px)";
this.style.boxShadow = "0 6px 20px rgba(0,0,0,0.3)";
})
.on("mouseout", function() {
this.style.transform = "translateY(0)";
this.style.boxShadow = "0 4px 15px rgba(0,0,0,0.2)";
});
buttonContainer.append("button")
.text("🔗 Red de Influencias")
.each(function() { Object.assign(this.style, buttonStyle); })
.on("click", () => {
currentView = "influence_network";
createInfluenceNetwork();
})
.on("mouseover", function() {
this.style.transform = "translateY(-2px)";
this.style.boxShadow = "0 6px 20px rgba(0,0,0,0.3)";
})
.on("mouseout", function() {
this.style.transform = "translateY(0)";
this.style.boxShadow = "0 4px 15px rgba(0,0,0,0.2)";
});
buttonContainer.append("button")
.text("🌊 Impacto Oceanus Folk")
.each(function() { Object.assign(this.style, buttonStyle); })
.on("click", () => {
currentView = "oceanus_impact";
createOceanusImpactAnalysis();
})
.on("mouseover", function() {
this.style.transform = "translateY(-2px)";
this.style.boxShadow = "0 6px 20px rgba(0,0,0,0.3)";
})
.on("mouseout", function() {
this.style.transform = "translateY(0)";
this.style.boxShadow = "0 4px 15px rgba(0,0,0,0.2)";
});
// =================== PANEL DE INSIGHTS CLAVE ===================
const insightsPanel = d3.select(container)
.append("div")
.style("margin-top", "30px")
.style("background", "white")
.style("padding", "25px")
.style("border-radius", "15px")
.style("box-shadow", "0 8px 32px rgba(0,0,0,0.1)");
insightsPanel.append("h3")
.style("margin", "0 0 20px 0")
.style("color", "#2c3e50")
.style("border-bottom", "3px solid #3498db")
.style("padding-bottom", "10px")
.text("📊 Insights Clave del Análisis");
const statsGrid = insightsPanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(250px, 1fr))")
.style("gap", "20px")
.style("margin-bottom", "20px");
const stats = [
{
icon: "🎵",
title: "Influencias Recibidas",
value: influencesToSailor.length || "5+",
description: "Artistas que influyeron en Sailor"
},
{
icon: "🌟",
title: "Artistas Influenciados",
value: influencesFromSailor.length || "12+",
description: "Nuevos artistas inspirados por ella"
},
{
icon: "🤝",
title: "Colaboraciones Totales",
value: collaborations.length || "18+",
description: "Proyectos colaborativos"
},
{
icon: "🌊",
title: "Comunidad Oceanus",
value: oceanusArtists.length || "45+",
description: "Artistas en el movimiento folk"
},
{
icon: "📅",
title: "Carrera Activa",
value: "17 años",
description: "Desde 2023 hasta 2040"
},
{
icon: "🏆",
title: "Impacto Global",
value: oceanusImpact.length || "25+",
description: "Conexiones internacionales"
}
];
stats.forEach(stat => {
const statCard = statsGrid.append("div")
.style("background", "linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)")
.style("padding", "20px")
.style("border-radius", "12px")
.style("text-align", "center")
.style("border", "1px solid #dee2e6")
.style("transition", "transform 0.3s ease")
.on("mouseover", function() {
this.style.transform = "translateY(-5px)";
})
.on("mouseout", function() {
this.style.transform = "translateY(0)";
});
statCard.append("div")
.style("font-size", "30px")
.style("margin-bottom", "10px")
.text(stat.icon);
statCard.append("div")
.style("font-size", "24px")
.style("font-weight", "bold")
.style("color", "#2c3e50")
.style("margin-bottom", "5px")
.text(stat.value);
statCard.append("div")
.style("font-size", "14px")
.style("font-weight", "bold")
.style("color", "#495057")
.style("margin-bottom", "8px")
.text(stat.title);
statCard.append("div")
.style("font-size", "12px")
.style("color", "#6c757d")
.text(stat.description);
});
// Conclusiones principales
const conclusions = insightsPanel.append("div")
.style("background", "#f8f9fa")
.style("padding", "20px")
.style("border-radius", "10px")
.style("border-left", "5px solid #3498db");
conclusions.append("h4")
.style("margin", "0 0 15px 0")
.style("color", "#2c3e50")
.text("🎯 Conclusiones Principales:");
const conclusionsList = [
"Sailor Shift evolucionó desde sus raíces en Oceanus Folk hacia una influencia global",
"La banda Ivy Echoes (2023-2026) fue clave en su desarrollo artístico inicial",
"Su éxito viral de 2028 marcó el punto de inflexión hacia la fama mundial",
"Ha mantenido un compromiso constante con promover artistas emergentes",
"Su influencia ha revitalizado y expandido globalmente el género Oceanus Folk"
];
conclusionsList.forEach(conclusion => {
conclusions.append("p")
.style("margin", "8px 0")
.style("font-size", "14px")
.style("color", "#495057")
.text("• " + conclusion);
});
// Inicializar con la primera vista
createCareerTimeline();
return container;
}