${parentPercentage.toFixed(1)}% de ${d.parent.data.name}
`;
}
tooltipHTML += `
Ruta: ${name(d)}
`;
// Show tooltip
tooltip
.html(tooltipHTML)
.style("opacity", 1);
// Posicionar tooltip inteligentemente
const tooltipElement = tooltip.node();
const pos = positionTooltip(event, tooltipElement);
tooltip
.style("left", pos.left + "px")
.style("top", pos.top + "px");
return true;
}
// Función para ocultar tooltip
function hideTooltip() {
tooltip.style("opacity", 0);
d3.selectAll("rect.highlight").classed("highlight", false);
}
// Track index for color assignment
let nodeIndex = 0;
// Crear rectángulos con eventos según el dispositivo
node.selectAll("rect")
.data(d => [d])
.join("rect")
.attr("id", d => `leaf-${d.data.name.replace(/\s+/g, '-')}-${Math.random().toString(36).substr(2, 9)}`)
.attr("fill", d => {
// Asignar color único basado en posición
const color = getColor(d, nodeIndex);
nodeIndex++;
return color;
})
.attr("stroke", "#fff")
.attr("stroke-width", 1);
// Asignar eventos según el dispositivo
if (isTouch) {
// Para dispositivos táctiles
node.filter(d => d === root ? d.parent : d.children)
.attr("cursor", "pointer")
.on("touchstart", function(event, d) {
event.preventDefault();
event.stopPropagation();
const currentTime = new Date().getTime();
const timeDiff = currentTime - lastTouchTime;
const sameTarget = (lastTouchTarget === this);
if (timeDiff < TOUCH_DELAY && timeDiff > 0 && sameTarget) {
// Es un doble toque en el mismo elemento - hacer zoom
clearTimeout(touchTimer);
lastTouchTime = 0;
lastTouchTarget = null;
if (d === root) {
zoomout(root);
} else {
zoomin(d);
}
// Ocultar tooltip si está visible
hideTooltip();
} else {
// Es un toque simple - guardar referencia
lastTouchTime = currentTime;
lastTouchTarget = this;
// Mostrar tooltip inmediatamente
showTooltip(event, d, this);
// Configurar temporizador para ocultar tooltip después de 3 segundos
clearTimeout(touchTimer);
touchTimer = setTimeout(() => {
hideTooltip();
}, 3000);
}
});
// Ocultar tooltip al tocar fuera o al hacer scroll
document.addEventListener('touchstart', function(event) {
if (!event.target.closest('rect')) {
hideTooltip();
}
});
} else {
// Para escritorio: comportamiento original
node.filter(d => d === root ? d.parent : d.children)
.attr("cursor", "pointer")
.on("click", (event, d) => {
d === root ? zoomout(root) : zoomin(d);
// Ocultar tooltip al hacer zoom
hideTooltip();
});
// Eventos de mouse para tooltips
node.selectAll("rect")
.on("mouseover", function(event, d) {
showTooltip(event, d, this.parentNode);
})
.on("mousemove", function(event) {
// Solo mover tooltip si está visible
if (tooltip.style("opacity") > 0) {
const tooltipElement = tooltip.node();
const pos = positionTooltip(event, tooltipElement);
tooltip
.style("left", pos.left + "px")
.style("top", pos.top + "px");
}
})
.on("mouseout", function(event, d) {
hideTooltip();
});
}
node.append("text")
.attr("font-weight", d => d === root ? "bold" : null)
.attr("fill", d => d === root ? "#000" : "#fff")
.attr("pointer-events", "none")
.selectAll("tspan")
.data(d => {
const displayName = d === root ? name(d) : d.data.name;
const words = displayName.split(/(?=[A-Z][^A-Z])/g);
return words.concat(format(d.value));
})
.join("tspan")
.attr("x", 3)
.attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
.attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 1 : 1)
.attr("font-weight", (d, i, nodes) => i === nodes.length - 1 ? "normal" : null)
.text(d => d);
group.call(position, root);
}
function position(group, root) {
group.selectAll("g")
.attr("transform", d => d === root ? `translate(0,-50)` : `translate(${x(d.x0)},${y(d.y0)})`)
.select("rect")
.attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
.attr("height", d => d === root ? 50 : y(d.y1) - y(d.y0));
// Ajustar visibilidad del texto
group.selectAll("text")
.each(function(d) {
const rect = d3.select(this.parentNode).select("rect");
const rectWidth = parseFloat(rect.attr("width"));
const rectHeight = parseFloat(rect.attr("height"));
if (rectWidth < 5 || rectHeight < 2) {
d3.select(this).style("display", "none");
} else {
d3.select(this).style("display", null);
}
});
}
// When zooming in, draw the new nodes on top, and fade them in.
function zoomin(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.append("g").call(render, d);
x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);
svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.call(position, d.parent))
.call(t => group1.transition(t)
.attrTween("opacity", () => d3.interpolate(0, 1))
.call(position, d));
}
// When zooming out, draw the old nodes on top, and fade them out.
function zoomout(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.insert("g", "*").call(render, d.parent);
x.domain([d.parent.x0, d.parent.x1]);
y.domain([d.parent.y0, d.parent.y1]);
svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.attrTween("opacity", () => d3.interpolate(1, 0))
.call(position, d))
.call(t => group1.transition(t)
.call(position, d.parent));
}
return svg.node();
}
// Función para cargar datos desde la URL
function loadDataAndCreateChart() {
const dataUrl = 'https://resquivel0810.github.io/resquivel/data/cine-mex-00-24.json';
// Mostrar mensaje de carga
const loadingElement = d3.select("#domainDrillDown");
loadingElement.html('
Cargando datos del cine mexicano 2000-2024...
');
// Cargar datos desde la URL
d3.json(dataUrl)
.then(data => {
// Limpiar el contenedor
loadingElement.html('');
// Crear el gráfico con los datos cargados
createChart(data);
})
.catch(error => {
console.error('Error al cargar los datos:', error);
loadingElement.html(`
Error al cargar los datos
No se pudieron cargar los datos desde: ${dataUrl}
Error: ${error.message}
`);
});
}
// Crear el gráfico cuando el DOM esté cargado
document.addEventListener('DOMContentLoaded', function() {
loadDataAndCreateChart();
});