Public
Edited
Jun 4, 2024
Fork of Tooltip
1 fork
Insert cell
Insert cell
predefined = {
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

// Add a line for each link, and a circle for each node.
const link = svg
.append("g")
.attr("stroke", "#CCCCCC")
.attr("stroke-opacity", 0.61)
.selectAll()
.data(links)
.join("line")
.attr("stroke-width", 2)
.attr("class", "link")
.style("opacity", 1); // Initial opacity set to 0

const node = svg
.append("g")
.attr("stroke", "#CCCCCC")
.attr("stroke-width", 1.5)
.selectAll()
.data(nodes)
.join("circle")
.attr("r", 5)
.attr("fill", "lightblue")
.attr("class", "node")
.style("opacity", 0); // Initial opacity set to 0

// original tooltip text
// node.append("title").text((d) => d.productId);
link
.attr("x1", (d) => {
// console.log(d.source);
return d.source.x;
})
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);

node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);

// Helper functions for animations
function animateNodes(nodesToAnimate, duration, onComplete) {
gsap.to(nodesToAnimate, {
duration: duration,
opacity: 1,
ease: "bounce.out",
onComplete: onComplete
// stagger: 0.01
});
}

svg.selectAll(".link").each(function () {
const linkElement = d3.select(this);
const totalLength = calculateLinkLength(linkElement.data()[0]);
linkElement
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength);
});

// Animate links function
function animateLinks(linksToAnimate, duration, onComplete) {
linksToAnimate.forEach((linkElement) => {
const linkSelection = d3.select(linkElement);
const totalLength = calculateLinkLength(linkSelection.data()[0]);

gsap.to(linkSelection.nodes(), {
strokeDashoffset: 0,
duration: duration,
ease: "bounce.out"
});
});
setTimeout(onComplete, duration * 1000); // Call onComplete after the animations
}

// Calculate link length function (remains the same)
function calculateLinkLength(linkData) {
const dx = linkData.target.x - linkData.source.x;
const dy = linkData.target.y - linkData.source.y;
return Math.sqrt(dx * dx + dy * dy);
}

// Identify first and second-level nodes
const firstLevelNodeIds = new Set();
const secondLevelNodeIds = new Set();

links.forEach((link) => {
firstLevelNodeIds.add(link.source.productId);
if (!firstLevelNodeIds.has(link.target.productId)) {
secondLevelNodeIds.add(link.target.productId);
}
});

// Select first and second-level nodes and links
const firstLevelNodes = svg
.selectAll(".node")
.filter((d) => firstLevelNodeIds.has(d.productId))
.nodes();
const firstLevelLinks = svg
.selectAll(".link")
.filter((d) => firstLevelNodeIds.has(d.source.productId))
.nodes();
const secondLevelNodes = svg
.selectAll(".node")
.filter((d) => secondLevelNodeIds.has(d.productId))
.nodes();

// Animate first-level nodes
animateNodes(firstLevelNodes, 1, () => {
// After first-level nodes, animate links
animateLinks(firstLevelLinks, 4, () => {
// Finally, animate second-level nodes
animateNodes(secondLevelNodes, 3, () => {
console.log("Animation complete");
});
});
});

return svg.node();
}
Insert cell
Insert cell
// data = FileAttachment("miserables.json").json()
Insert cell
Insert cell
Insert cell
new Set(metaData.map((d) => d.productSector.productId))
Insert cell
Insert cell
Insert cell
Insert cell
nodes = {
const nodes = data.nodes.map((d) => ({ ...d }));
nodes.forEach((d) => {
d.x = xScale(d.x);
d.y = yScale(d.y);
});

return nodes;
}
Insert cell
Insert cell
// chart = {
// // Specify the dimensions of the chart.

// const height = 1200;

// // Specify the color scale.
// const color = d3.scaleOrdinal(d3.schemeCategory10);

// // The force simulation mutates links and nodes, so create a copy
// // so that re-evaluating this cell produces the same result.
// const links = data.links.map((d) => ({ ...d }));
// const nodes = data.nodes.map((d) => ({ ...d }));
// nodes.forEach((node) => {
// node.x = 0.1 * width;
// node.y = 0.1 * height;
// });

// // Create the simulation with adjusted forces
// const simulation = d3
// .forceSimulation(nodes)
// .force(
// "link",
// d3.forceLink(links).id((d) => d.productId)
// )
// .force("charge", d3.forceManyBody().strength(-30)) // Adjusted charge strength
// .force("center", d3.forceCenter(width / 2, height / 2))
// .stop()
// .tick(n_frames_to_simulate);

// // Create the SVG container.
// const svg = d3
// .create("svg")
// .attr("width", width)
// .attr("height", height)
// .attr("viewBox", [0, 0, width, height])
// .attr("style", "max-width: 100%; height: auto;");

// // Add a line for each link, and a circle for each node.
// const link = svg
// .append("g")
// .attr("stroke", "#CCCCCC")
// .attr("stroke-opacity", 0.6)
// .selectAll()
// .data(links)
// .join("line")
// .attr("stroke-width", 3)
// .attr("class", "link")
// .style("opacity", 0); // Initial opacity set to 0

// const node = svg
// .append("g")
// .attr("stroke", "#CCCCCC")
// .attr("stroke-width", 1.5)
// .selectAll()
// .data(nodes)
// .join("circle")
// .attr("r", 5)
// .attr("fill", "lightblue")
// .attr("class", "node")
// .style("opacity", 0); // Initial opacity set to 0

// node.append("title").text((d) => d.productId);
// 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("cx", (d) => d.x).attr("cy", (d) => d.y);
// node
// .transition()
// .duration(7500) // Duration of fade-in
// .delay((d, i) => i * 5) // Stagger the fade-in of nodes
// .style("opacity", 1);

// link
// .transition()
// .duration(7500)
// .delay((d, i) => i * 5)
// .style("opacity", 1);

// // Default styles
// const defaultNodeStyle = { stroke: "#fff", strokeWidth: 1.5 };
// const defaultLinkStyle = {
// stroke: "#999",
// strokeWidth: 3
// };

// // Highlighted styles
// const highlightStyle = { stroke: "red", strokeWidth: 3 };

// node.on("mouseover", handleMouseOver).on("mouseout", handleMouseOut);

// function handleMouseOver(event, d) {
// // Highlight the node with transition
// d3.select(this)
// .transition()
// .duration(transitionTime) // Duration in milliseconds
// .style("stroke", highlightStyle.stroke)
// .style("stroke-width", highlightStyle.strokeWidth);

// // Highlight the links and connected nodes with transition
// link
// .transition()
// .duration(transitionTime)
// .style("stroke", (l) =>
// l.source === d || l.target === d
// ? highlightStyle.stroke
// : defaultLinkStyle.stroke
// )
// .style("stroke-width", (l) =>
// l.source === d || l.target === d
// ? highlightStyle.strokeWidth
// : defaultLinkStyle.strokeWidth(l)
// );

// node
// .transition()
// .duration(transitionTime)
// .style("stroke", (n) =>
// isConnected(d, n) ? highlightStyle.stroke : defaultNodeStyle.stroke
// )
// .style("stroke-width", (n) =>
// isConnected(d, n)
// ? highlightStyle.strokeWidth
// : defaultNodeStyle.strokeWidth
// );

// // Show tooltip
// showTooltip(event, d);
// }

// function handleMouseOut(event, d) {
// // Revert to default styles with transition
// link
// .transition()
// .duration(transitionTime / 2)
// .style("stroke", defaultLinkStyle.stroke)
// .style("stroke-width", defaultLinkStyle.strokeWidth);

// node
// .transition()
// .duration(transitionTime / 2)
// .style("stroke", defaultNodeStyle.stroke)
// .style("stroke-width", defaultNodeStyle.strokeWidth);

// // Hide tooltip
// hideTooltip();
// }

// function isConnected(a, b) {
// return links.some(
// (l) =>
// (l.source === a && l.target === b) || (l.source === b && l.target === a)
// );
// }
// function showTooltip(event, d) {
// const tooltip = d3
// .select("body")
// .append("div")
// .attr("class", "tooltip")
// .style("opacity", 0);

// tooltip.transition().duration(200).style("opacity", 0.9);

// tooltip
// .html("Target: " + d.Target) // Adjust according to your data structure
// .style("left", event.pageX + "px")
// .style("top", event.pageY - 28 + "px");
// }

// function hideTooltip() {
// d3.select(".tooltip").remove();
// }

// // When this cell is re-run, stop the previous simulation. (This doesn’t
// // really matter since the target alpha is zero and the simulation will
// // stop naturally, but it’s a good practice.)
// invalidation.then(() => simulation.stop());

// return svg.node();
// }
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
gsap = gs.gsap
Insert cell
gs = require("https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.1/gsap.min.js")
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