async function BrushableScatterPlot(dataToPlot, options) {
options = {
interactive: true,
colorScheme: undefined,
colorSchemeNominal: "tableau20",
colorSchemeQuantitative: "brownbluegreen",
colorType: undefined,
x: "x",
y: "y",
color: null,
size: null,
shape: null,
id: null,
tooltip: undefined,
colorDomain: null,
title: `${dataToPlot.length} documents by similarity`,
width: 600,
height: 500,
sizeRange: undefined,
vegaSpecWrapper: (d) => d,
colorOnHover: true,
shapeDomain: undefined,
...options
};
if (!options.id) {
if (dataToPlot?.length && "id" in dataToPlot[0]) {
options.id = "id";
} else {
dataToPlot.map((d, i) => (d.id = i));
options.id = "id";
}
}
options.tooltip = options.tooltip || [
options.id,
options.x,
options.y,
options.color,
options.size
];
const vegaView = options.vegaSpecWrapper(getVegaView(dataToPlot, options));
let brushed = dataToPlot;
let clicked = [];
//stores data for double click
let previousClickedIds = [];
// *** Get Vega ***
const vegaReactiveChart = await (options.interactive
? vegaSelected(vegaView.toSpec(), { renderer: "canvas" })
: vegaView.render());
const target = html`${vegaReactiveChart}`;
console.log("🗺️ new Brushablescatterplot", options.interactive, vegaReactiveChart, options);
function setValue(brushed, clicked) {
target.value = { brushed, clicked };
// No need to disptach the input event as the one from the inner widget will bubble out
// target.dispatchEvent(new CustomEvent("input", { bubbles: true }));
}
// Added a single event listener to handle interactions, simplifying the logic by adding both drag and click handling here
vegaReactiveChart.addEventListener("input", (evt) => {
console.log("🔢 Brushable scatterplot input", evt);
evt.stopPropagation();
//Drag interaction: Filters the data points within the drag selection region
if (vegaReactiveChart.value.drag) {
brushed = vegaReactiveChart.value.drag
? dataToPlot.filter(
(d) =>
d.x >= vegaReactiveChart.value.drag.x[0] &&
d.x <= vegaReactiveChart.value.drag.x[1] &&
d.y >= vegaReactiveChart.value.drag.y[0] &&
d.y <= vegaReactiveChart.value.drag.y[1]
)
: dataToPlot;
}
// Double-click interaction: Handles clicks on the chart to open a URL if the item was already clicked
if (vegaReactiveChart.value.click) {
const newlyClicked = dataToPlot.filter((d) =>
vegaReactiveChart.value.click?.id.includes(d.id)
);
const newlyClickedIds = newlyClicked.map((d) => d.id);
// Checks if the clicked item was already selected (double-click logic)
const wasAlreadySelected = newlyClickedIds.some(id => previousClickedIds.includes(id));
if (wasAlreadySelected) {
const paper = dataToPlot.find((d) => d.id === newlyClickedIds[0]);
// Opens the URL of the double-clicked item in a new tab or window
if (paper?.url) window.open(paper.url, "_blank");
}
// Updates clicked and brushed states to reflect the new selection
clicked = newlyClicked;
previousClickedIds = newlyClickedIds;
brushed = dataToPlot; // Clears brush on a click interaction
}
setValue(brushed, clicked);
});
setValue(brushed, clicked);
return target;
}