Published
Edited
Nov 28, 2021
4 stars
Insert cell
Insert cell
Insert cell
chart = html`
<span style="padding:2px;color:white;background-color:#54278f">
<strong>Demonetized<strong>
</span>

<span style="padding:2px;color:black;background-color:#f2f0f799">
<strong>Monetized</strong>
</span>

${props.div}
`
Insert cell
Insert cell
props = renderSVGGl()
Insert cell
Insert cell
Insert cell
Insert cell
{
let a = showOnlyHighlighted;
props.redrawCtx;
}
Insert cell
viewof showOnlyHighlighted = Inputs.toggle({
label: "Filter Only Demonetized Words"
})
Insert cell
Insert cell
generateTooltipHTMLContent = (d) => `x: ${d.x}, y: ${d.y}`
Insert cell
Insert cell
getCircleAlpha = (d) => (d[COLS.toggle] ? 0.6 : 1)
Insert cell
getCircleColor = (d) => (d[COLS.toggle] ? 0xf2f0f7 : 0x54278f)
Insert cell
Insert cell
COLS = {
return {
toggle: "Monetized"
};
}
Insert cell
filterData = showOnlyHighlighted ? data.filter((d) => !d[COLS.toggle]) : data
Insert cell
data = _data.map(({ x, y, Monetized }) => ({ x, y, Monetized }))
Insert cell
Insert cell
Insert cell
annotations = [] || [
{
x: 615,
y: 385.99999999999994,
dx: -42,
dy: 67,
subject: { radius: 48.485281374238554 },
note: { title: "Profanity" }
},
{
x: 687,
y: 156,
dx: 57,
dy: -52,
subject: { radius: 50 },
note: { title: "Country Terms" }
},
{
x: 738,
y: 334,
dx: 62,
dy: 52,
subject: { radius: 50 },
note: { title: "Sex Terms", align: "left" }
},
{
x: 415,
y: 92.00000000000006,
dx: 24,
dy: -29,
subject: { radius: 20 },
note: { title: "Male Names" }
},
{
x: 270,
y: 63.99999999999994,
dx: -42,
dy: 16,
subject: { radius: 20 },
note: { title: "Female Names" }
},
{
x: 781.9999999999999,
y: 245.00000000000003,
dx: 32,
dy: -34,
subject: { radius: 20 },
note: { title: "Drugs" }
},
{
x: 871.7843307948311,
y: 273,
dx: -49,
dy: 59,
subject: { radius: 32.82842712474619 },
note: { title: "Diseases", align: "left" }
},
{
x: 804,
y: 462,
dx: -128,
dy: 30,
subject: { radius: 44.14213562373093 },
note: { title: "Insults", align: "left" }
},
{
x: 69,
y: 310,
dx: -50,
dy: 66,
subject: { radius: 20 },
note: { title: "Money Terms", align: "left" }
},
{
x: 265,
y: 207,
dx: -113,
dy: -17,
subject: { radius: 28.485281374238575 },
note: { title: "Gaming", align: "left" }
},
{
x: 190,
y: 435.99999999999994,
dx: 29,
dy: 27,
subject: { radius: 20 },
note: { title: "Time Terms" }
},
{
x: 97,
y: 276,
dx: -57,
dy: -29,
subject: { radius: 20 },
note: { title: "Politics", align: "left" }
}
]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function renderSVGGl() {
// 1. CREATE & SETUP CORE HTML PARENT ELEMENTS
const div = DOM.element("div");
div.style.position = "relative";
const svg = d3.select(DOM.svg(width, height));
const tooltip = d3
.select(div)
.append("div")
.attr("class", "tooltip")
.style("display", "none");

svg.style("position", "relative");
svg.style("z-index", "1000");
svg.style("display", "block");
svg.style("width", "100%");

pixiApp.view.style.position = "absolute";
pixiApp.view.style.width = "100%";
pixiApp.view.style.height = "auto";

div.appendChild(pixiApp.view);
div.appendChild(svg.node());

document.body.appendChild(div);

// scales & veronoi
const xScaleZoom = xScale.copy();
const yScaleZoom = yScale.copy();

const delaunay = d3.Delaunay.from(
filterData.map((e) => [xScale(e.x) * maxZoom, yScale(e.y) * maxZoom])
);
const voronoi = delaunay.voronoi(extent.flat().map((e) => e * maxZoom));

let currScale = 1;

// 2. CONTROL HOW CIRCLES GET REDRAWN AFTER UPDATES (eg zoom)
const redrawCtx = () => {
pixiPoints.forEach((e, i) => {
e.x = xScaleZoom(filterData[i].x);
e.y = yScaleZoom(filterData[i].y);

if (e.selected) {
e.scale.x = 1.5 * dotScale(currScale);
e.scale.y = 1.5 * dotScale(currScale);
e.tint = getCircleColor(filterData[i]);
e.alpha = 0.4;
} else {
e.scale.x = dotScale(currScale);
e.scale.y = dotScale(currScale);
e.tint = getCircleColor(filterData[i]);
e.alpha = getCircleAlpha(filterData[i]);
}
});
};

// 3. IMPLEMENT ZOOM HANDLERS
function zoomed(event, d) {
currScale = event.transform.k;

xScaleZoom.range(
[margin.left, width - margin.right].map((d) => event.transform.applyX(d))
);
yScaleZoom.range(
[margin.top, height - margin.bottom].map((d) => event.transform.applyY(d))
);

updateAnnotations(xScaleZoom, yScaleZoom, currScale);

redrawCtx();
}

const zoom = d3
.zoom()
.scaleExtent([1, maxZoom])
.translateExtent([
[0, 0],
[width, height]
])
.extent(extent)
.on("zoom", zoomed);

let tick = false;
svg.on("mousemove", (event, d) => {
if (!tick) {
window.requestAnimationFrame(() => {
mouseMoved(event);
tick = false;
});
tick = true;
}
});

// 4. HANDLE MOUSE INTERACTION
function mouseMoved(event) {
const [rmx, rmy] = [event.layerX, event.layerY];
const [mx, my] = [
xScale(xScaleZoom.invert(rmx)) * maxZoom,
yScale(yScaleZoom.invert(rmy)) * maxZoom
];
const tooltip_width = parseInt(tooltip.style("width"));

pointsContainer.children.forEach((d) => {
d.selected = false;
});

const voronoi_pt_idx = delaunay.find(mx, my);
const points_container_last_idx = pointsContainer.children.length - 1;

if (pointsContainer.children[points_container_last_idx].selected) {
pointsContainer.children[points_container_last_idx].selected = false; // deselect previous element
tooltip.style("display", "none");
}

if (voronoi_pt_idx !== null) {
const d = filterData[voronoi_pt_idx];
tooltip
.style("display", "block")
.attr("data-px", `${d.x}`)
.attr("data-py", d.y)
.style("left", `${xScaleZoom(d.x)}px`)
.style("top", `${yScaleZoom(d.y)}px`)
.html(generateTooltipHTMLContent(d));

const ind = pointsContainer.children.findIndex(
(e) => e.orig_idx === voronoi_pt_idx
);
pointsContainer.children[ind].selected = true; //select over element
const temp = pointsContainer.children[points_container_last_idx];
pointsContainer.children[points_container_last_idx] =
pointsContainer.children[ind];
pointsContainer.children[ind] = temp;

redrawCtx();
} else {
tooltip.style("display", "none");
}
}

// FINALLY, BIND IT ALL TOGETHER: ---------------------------------------

// 5. HOOK UP ZOOM HANDLERS TO svg CANVAS
svg
.on("click", (e) => {
const [rmx, rmy] = [d3.event.layerX, d3.event.layerY];
const [mx, my] = [xScale.invert(rmx), yScale.invert(rmy)];
})
.call(zoom)
.call(zoom.scaleTo, 1)
.on("dblclick.zoom", (e) => {
svg.transition().duration(750).call(zoom.transform, d3.zoomIdentity);
});

// 6. ADD ANNOTATIONS
svg
.append("g")
.attr("class", "annotation-group")
.style("font-size", "1.2em")
.call(makeAnnotations);
return { div, redrawCtx };
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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