235 lines
7.8 KiB
JavaScript
235 lines
7.8 KiB
JavaScript
// D3.js Animations for Homepage - Training & Learning Focus
|
|
function initD3Animation() {
|
|
const container = d3.select("#d3-animation");
|
|
if (container.empty()) return;
|
|
|
|
const containerRect = container.node().getBoundingClientRect();
|
|
const width = containerRect.width;
|
|
const height = containerRect.height || 400;
|
|
|
|
// Clear any existing SVG
|
|
container.selectAll("*").remove();
|
|
|
|
const svg = container.append("svg")
|
|
.attr("width", width)
|
|
.attr("height", height)
|
|
.attr("viewBox", `0 0 ${width} ${height}`)
|
|
.style("background", "transparent")
|
|
.style("overflow", "hidden");
|
|
|
|
// Create a main group for all elements
|
|
const mainGroup = svg.append("g")
|
|
.attr("class", "main-group");
|
|
|
|
// Create animated network visualization representing Odoo training modules
|
|
const nodes = [
|
|
{ id: "Fundamentos", group: 1, size: 35, level: 1 },
|
|
{ id: "CRM Básico", group: 2, size: 25, level: 2 },
|
|
{ id: "Ventas", group: 2, size: 25, level: 2 },
|
|
{ id: "Inventario", group: 3, size: 25, level: 2 },
|
|
{ id: "Contabilidad", group: 4, size: 30, level: 2 },
|
|
{ id: "CRM Avanzado", group: 2, size: 20, level: 3 },
|
|
{ id: "Reportes", group: 4, size: 20, level: 3 },
|
|
{ id: "Integraciones", group: 3, size: 25, level: 3 },
|
|
{ id: "Certificación", group: 1, size: 30, level: 4 },
|
|
{ id: "Especialización", group: 1, size: 25, level: 4 }
|
|
];
|
|
|
|
const links = [
|
|
{ source: "Fundamentos", target: "CRM Básico", value: 3 },
|
|
{ source: "Fundamentos", target: "Ventas", value: 3 },
|
|
{ source: "Fundamentos", target: "Inventario", value: 3 },
|
|
{ source: "Fundamentos", target: "Contabilidad", value: 3 },
|
|
{ source: "CRM Básico", target: "CRM Avanzado", value: 2 },
|
|
{ source: "Ventas", target: "CRM Avanzado", value: 2 },
|
|
{ source: "Contabilidad", target: "Reportes", value: 3 },
|
|
{ source: "Inventario", target: "Integraciones", value: 3 },
|
|
{ source: "CRM Avanzado", target: "Certificación", value: 3 },
|
|
{ source: "Reportes", target: "Certificación", value: 2 },
|
|
{ source: "Integraciones", target: "Especialización", value: 2 },
|
|
{ source: "Certificación", target: "Especialización", value: 3 }
|
|
];
|
|
|
|
// Color scale with orange theme
|
|
const color = d3.scaleOrdinal()
|
|
.domain([1, 2, 3, 4])
|
|
.range(["#FF6B35", "#FF8A50", "#FFA726", "#FFB74D"]);
|
|
|
|
// Add boundary force to keep nodes within container
|
|
const boundaryForce = () => {
|
|
nodes.forEach(node => {
|
|
node.x = Math.max(node.size, Math.min(width - node.size, node.x));
|
|
node.y = Math.max(node.size, Math.min(height - node.size, node.y));
|
|
});
|
|
};
|
|
|
|
// Create force simulation with boundaries
|
|
const simulation = d3.forceSimulation(nodes)
|
|
.force("link", d3.forceLink(links).id(d => d.id).distance(80))
|
|
.force("charge", d3.forceManyBody().strength(-250))
|
|
.force("center", d3.forceCenter(width / 2, height / 2))
|
|
.force("collision", d3.forceCollide().radius(d => d.size + 8))
|
|
.force("boundary", boundaryForce);
|
|
|
|
// Create links
|
|
const link = mainGroup.append("g")
|
|
.attr("class", "links")
|
|
.selectAll("line")
|
|
.data(links)
|
|
.enter().append("line")
|
|
.attr("stroke", "#FF6B35")
|
|
.attr("stroke-opacity", 0.3)
|
|
.attr("stroke-width", d => Math.sqrt(d.value));
|
|
|
|
// Create nodes container
|
|
const node = mainGroup.append("g")
|
|
.attr("class", "nodes")
|
|
.selectAll("g")
|
|
.data(nodes)
|
|
.enter().append("g")
|
|
.call(d3.drag()
|
|
.on("start", dragstarted)
|
|
.on("drag", dragged)
|
|
.on("end", dragended));
|
|
|
|
// Add circles
|
|
node.append("circle")
|
|
.attr("r", d => d.size)
|
|
.attr("fill", d => color(d.group))
|
|
.attr("stroke", "#1a1a1a")
|
|
.attr("stroke-width", 2)
|
|
.style("cursor", "pointer")
|
|
.on("mouseover", function(event, d) {
|
|
d3.select(this)
|
|
.transition()
|
|
.duration(200)
|
|
.attr("r", d.size * 1.2);
|
|
})
|
|
.on("mouseout", function(event, d) {
|
|
d3.select(this)
|
|
.transition()
|
|
.duration(200)
|
|
.attr("r", d.size);
|
|
});
|
|
|
|
// Add text labels
|
|
node.append("text")
|
|
.text(d => d.id)
|
|
.attr("text-anchor", "middle")
|
|
.attr("dy", ".35em")
|
|
.style("fill", "#1a1a1a")
|
|
.style("font-size", "11px")
|
|
.style("font-weight", "700")
|
|
.style("pointer-events", "none");
|
|
|
|
// Add animated pulses
|
|
node.append("circle")
|
|
.attr("r", d => d.size)
|
|
.attr("fill", "none")
|
|
.attr("stroke", d => color(d.group))
|
|
.attr("stroke-width", 2)
|
|
.attr("opacity", 0)
|
|
.style("pointer-events", "none")
|
|
.each(function(d, i) {
|
|
d3.select(this)
|
|
.transition()
|
|
.delay(i * 200)
|
|
.duration(2000)
|
|
.attr("r", d => d.size * 2)
|
|
.attr("opacity", 0)
|
|
.on("end", function repeat() {
|
|
d3.select(this)
|
|
.attr("r", d.size)
|
|
.attr("opacity", 0.5)
|
|
.transition()
|
|
.duration(2000)
|
|
.attr("r", d.size * 2)
|
|
.attr("opacity", 0)
|
|
.on("end", repeat);
|
|
});
|
|
});
|
|
|
|
// Update positions on tick
|
|
simulation.on("tick", () => {
|
|
// Apply boundary force
|
|
boundaryForce();
|
|
|
|
link
|
|
.attr("x1", d => d.source.x)
|
|
.attr("y1", d => d.source.y)
|
|
.attr("x2", d => d.target.x)
|
|
.attr("y2", d => d.target.y);
|
|
|
|
node
|
|
.attr("transform", d => `translate(${d.x},${d.y})`);
|
|
});
|
|
|
|
// Drag functions with boundaries
|
|
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 = Math.max(d.size, Math.min(width - d.size, event.x));
|
|
d.fy = Math.max(d.size, Math.min(height - d.size, event.y));
|
|
}
|
|
|
|
function dragended(event, d) {
|
|
if (!event.active) simulation.alphaTarget(0);
|
|
d.fx = null;
|
|
d.fy = null;
|
|
}
|
|
|
|
// Add floating particles with orange theme
|
|
const particles = mainGroup.append("g")
|
|
.attr("class", "particles");
|
|
|
|
function createParticle() {
|
|
const particle = particles.append("circle")
|
|
.attr("r", Math.random() * 2 + 1)
|
|
.attr("cx", Math.random() * width)
|
|
.attr("cy", height + 5)
|
|
.attr("fill", "#FF6B35")
|
|
.attr("opacity", 0.2);
|
|
|
|
particle.transition()
|
|
.duration(Math.random() * 8000 + 4000)
|
|
.ease(d3.easeLinear)
|
|
.attr("cy", -5)
|
|
.attr("opacity", 0)
|
|
.remove();
|
|
}
|
|
|
|
// Create particles periodically
|
|
setInterval(createParticle, 500);
|
|
}
|
|
|
|
// Initialize on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initD3Animation();
|
|
|
|
// Reinitialize on window resize
|
|
let resizeTimer;
|
|
window.addEventListener('resize', function() {
|
|
clearTimeout(resizeTimer);
|
|
resizeTimer = setTimeout(function() {
|
|
initD3Animation();
|
|
}, 250);
|
|
});
|
|
});
|
|
|
|
// Smooth scroll for anchor links
|
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
anchor.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
const target = document.querySelector(this.getAttribute('href'));
|
|
if (target) {
|
|
target.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
});
|
|
}); |