Published
Edited
Dec 7, 2021
1 fork
Insert cell
Insert cell
Insert cell
rawData = FileAttachment("top50cosmetics@2.json").json()
Insert cell
cleanedData = rawData.map((d) => ({
...d,
reviews: d.reviews.map((e) => ({
...e,
date: new Date(e.date.split('"')[1])
}))
}))
Insert cell
Insert cell
boundaryDates = d3.extent(
cleanedData.map((d) => d.reviews).flat(),
(d) => d.date
)
Insert cell
timeScale = d3
.scaleTime()
.domain(boundaryDates)
.range([margin, width - margin])
Insert cell
productScale = d3
.scalePoint()
.domain(cleanedData.map((d) => d.name))
.range([margin, height - margin])
Insert cell
Insert cell
Insert cell
Insert cell
{
const svg = d3.create("svg").attr("width", width).attr("height", height);
const bg = svg
.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "#445");

svg
.selectAll(".timelines")
.data(cleanedData)
.enter()
.append("line")
.attr("stroke", "white")
.attr("x1", margin)
.attr("x2", width - margin)
.attr("y1", (d) => productScale(d.name))
.attr("y2", (d) => productScale(d.name));

for (let product of cleanedData) {
svg
.selectAll(".reviewDots")
.data(product.reviews)
.enter()
.append("circle")
.attr("fill", (d) => d3.interpolateMagma(d.stars / 5))
.attr("cx", (d) => timeScale(d.date))
.attr("cy", (d) => productScale(product.name))
.attr("r", 3)
.attr("opacity", 0.5);
}

svg
.selectAll(".labels")
.data(boundaryDates)
.enter()
.append("text")
.attr("x", (d) => timeScale(d))
.attr("y", margin / 2)
.attr("fill", "white")
.attr("text-anchor", "middle")
.text((d) => d.toLocaleString().split(",")[0]);

return svg.node();
}
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