Public
Edited
Oct 26, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
multiChart = {
//replay on button click
replay2;

//define dimensions
let width;
width =
window.innerWidth > 500 ? window.innerWidth * 0.75 : window.innerWidth;
const height = 500;
const margin = {
top: height * 0.1,
right: height * 0.1,
bottom: height * 0.1,
left: height * 0.1
};

//get max of each variable
const xMax = 1.2;
const yMax = d3.max(multiEmojiData, (d) => d.favoriteCount);

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

//create scales
const xScale = d3
.scaleLinear()
.domain([0, xMax])
.nice()
.rangeRound([margin.left, width - margin.right]);
const yScale = d3
// .scaleSymlog() //scaleLog does not allow 0 in domain
// // .base(2)
// .constant(15000)
.scaleLinear()
.domain([0, yMax])
.nice()
.range([height - margin.top, margin.bottom]);

//declare image dimensions
const imageDims = { height: height * 0.075, width: height * 0.075 };

//declare x/y position getters
const xGet = (d) => xScale(d.diff);
const yGet = (d) => yScale(d.favoriteCount);
const xPos = (d) => xGet(d) - imageDims.width / 2;
const yPos = (d) => yGet(d) - imageDims.height / 2;

//create axes
const xAxis = d3.axisBottom(xScale).tickValues([0, 0.3, 0.6, 0.9]);
const yAxis = d3
.axisLeft(yScale)
.tickValues([0, 5000, 10000, 15000, 20000, 25000, 28500]);

//create tooltip
const tooltip = d3
.select("body")
.append("div")
.attr("class", "svg-tooltip")
.style("position", "absolute")
.style("visibility", "hidden");

//add x axis
svg
.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(xAxis)
.call((g) => g.select(".domain").remove())
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("y2", margin.top + margin.bottom - height)
.attr("stroke-opacity", 0.1)
)
.call((g) =>
g
.append("text")
.attr("x", width - margin.right)
.attr("y", margin.bottom - 10)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.attr("font-size", 14)
.text("Difference in emoji sentiment →")
)
.call(transitionOpacity);

//add y axis
svg
.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(yAxis)
.call((g) => g.select(".domain").remove())
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("x2", width - margin.left - margin.right)
.attr("stroke-opacity", 0.1)
)
.call((g) =>
g
.append("text")
.attr("x", margin.left)
.attr("y", margin.top - 5)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.attr("font-size", 14)
.text("↑ Favorites")
)
.call(transitionOpacity);

//add images
svg
.append("g")
.selectAll("svg")
.data(multiEmojiData)
.join("svg")
.attr("x", xPos)
.attr("y", yPos)
.attr("width", imageDims.width)
.attr("height", imageDims.height)
.attr("opacity", 0)
.call(emojiInteractions, tooltip, xGet, yGet, imageDims)
.transition()
.duration(1000)
.ease(d3.easeCubicOut)
.attr("x", xPos)
.attr("y", yPos)
.attr("opacity", 1);

svg
.selectAll("svg")
.data(multiEmojiData)
.join("svg")
.append("image")
.attr("xlink:href", (d) => d.media_url)
.attr("width", "100%")
.attr("height", "100%")
.attr("preserveAspectRatio", "xMidYMid slice")
.style("mix-blend-mode", "multiply") //these styles remove the white background
.style("filter", "contrast(1)") //these styles remove the white background
.style("cursor", "pointer")
.call(transitionOpacity, 500);

return svg.node();
}

//TODO: comments
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
transitionOpacity = function (selection, t = 1250, rest) {
selection.attr("opacity", 0).transition().duration(t).attr("opacity", 1);
}
Insert cell
emojiInteractions = function (
selection, // the element being selected
tooltip, // the tooltip element
xGet, // x scale getter
yGet, // y scale getter
imageDims, // imageDims object with width/height attributes
scaleFactor = 1.5 // how much to scale on hover
) {
selection
// MOUSEOVER
.on("mouseover", function (e, d) {
d3.select(this)
.transition()
.duration(250)
.attr("x", xGet(d) - (imageDims.width * scaleFactor) / 2)
.attr("y", yGet(d) - (imageDims.height * scaleFactor) / 2)
.attr("width", imageDims.width * scaleFactor)
.attr("height", imageDims.height * scaleFactor);
})
// CLICK
.on("click", function (e, d) {
// display tooltip
tooltip
.style("top", e.pageY - 5 + "px")
.style("left", e.pageX + 5 + "px")
.append("div")
.attr("id", "tweet-frame")
.style("z-index", 1000)
.node()
.appendChild(tweet(d.id));

// transition tooltip
tooltip.transition().duration(1500).style("opacity", "1");
})
// MOUSEOUT
.on("mouseout", function (e, d) {
// return emoji back to original size
d3.select(this)
.transition()
.duration(250)
.attr("x", xGet(d) - imageDims.width / 2)
.attr("y", yGet(d) - imageDims.height / 2)
.attr("width", imageDims.width)
.attr("height", imageDims.height);

// remove tooltip
tooltip.transition().duration(1000).style("opacity", "0");
tooltip.transition().delay(1000).select("#tweet-frame").remove();
});
}
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