Skip to content

Commit

Permalink
Remove unnecessary legend re-render (#70)
Browse files Browse the repository at this point in the history
* Don't rerender legend

* Ensure legend items aren't duplciated
  • Loading branch information
mkfreeman authored Nov 25, 2024
1 parent 634bcf2 commit 94b6632
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class DuckPlot {
undefined;
visibleSeries: string[] = [];
filteredData: Data | undefined = undefined;
chartElement: HTMLElement | null = null;
chartContainer: HTMLElement | null = null;
seriesDomain: number[] = [];

constructor(
Expand Down
8 changes: 4 additions & 4 deletions src/legend/legend.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
.dp-categories {
display: flex;
flex-wrap: nowrap;
user-select: none;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
}

.dp-category:not(:last-child) {
Expand All @@ -43,6 +39,10 @@
gap: 2px;
align-items: center;
text-wrap: nowrap;
user-select: none;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
}

/* Popover for extra categories */
Expand Down
36 changes: 23 additions & 13 deletions src/legend/legendCategorical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ export async function legendCategorical(
instance.plotObject?.scale("color")?.domain ?? []
)?.map((d) => `${d}`);

// Set the visible series to all categories if it's empty
if (instance.visibleSeries.length === 0) {
instance.visibleSeries = categories;
function isActive(category: string): boolean {
return (
instance.visibleSeries.length === 0 ||
instance.visibleSeries.includes(category)
);
}

const visibleCategories = instance.visibleSeries;
const colors = Array.from(instance.plotObject?.scale("color")?.range ?? []);
const options = await instance.derivePlotOptions();
const width = options.width || 500;
Expand Down Expand Up @@ -53,7 +54,7 @@ export async function legendCategorical(
categories.forEach((category, i) => {
const categoryDiv = document.createElement("div");
categoryDiv.className = `dp-category${
visibleCategories.includes(category) ? "" : " dp-inactive"
isActive(category) ? "" : " dp-inactive"
}`;

const square = document.createElement("div");
Expand Down Expand Up @@ -104,25 +105,34 @@ export async function legendCategorical(
// Shift-click: hide all others
if (mouseEvent.shiftKey) {
// If this is the only visible element, reset all to visible
if (
instance.visibleSeries.length === 1 &&
instance.visibleSeries.includes(elementId)
) {
instance.visibleSeries = categories;
if (instance.visibleSeries.length === 1 && isActive(elementId)) {
instance.visibleSeries = [];
} else {
instance.visibleSeries = [elementId]; // show only this one
}
} else {
// Regular click: toggle visibility of the clicked element
if (instance.visibleSeries.includes(elementId)) {
instance.visibleSeries = instance.visibleSeries.filter(
if (isActive(elementId)) {
const currentSeries = instance.visibleSeries.length
? instance.visibleSeries
: categories;
instance.visibleSeries = currentSeries.filter(
(id) => id !== elementId
); // Hide the clicked element
} else {
instance.visibleSeries.push(elementId); // Show the clicked element
}
}
instance.render();
// Update the active state of the items in the legend
legendElements.forEach((e) => {
if (isActive(`${e.textContent}`)) {
e.classList.remove("dp-inactive");
} else {
e.classList.add("dp-inactive");
}
});
// Rerender the plot, but not the legend itself
instance.render(false);
});
});
}
Expand Down
86 changes: 41 additions & 45 deletions src/render/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,67 +9,63 @@ export async function render(
instance: DuckPlot,
newLegend: boolean
): Promise<SVGElement | HTMLElement | null> {
const marks = instance.getAllMarkOptions();
const document = instance.isServer
? instance.jsdom.window.document
: undefined;
// Set this._sorts that is consumed by getAllMarkOptions
instance.setSorts();
const topLevelPlotOptions = getPlotOptions(instance);

// Generate Plot Options
const plotOptions = {
...topLevelPlotOptions,
marks,
...(document ? { document } : {}),
...getPlotOptions(instance),
marks: instance.getAllMarkOptions(),
...(instance.document ? { document: instance.document } : {}),
};

// Adjust margins UNLESS specified otherwise or missing font on the server
const serverWithoutFont = instance.isServer && !instance.font;
const autoMargin = serverWithoutFont
? false
: instance.config().autoMargin !== false;
// Detect if the plot should auto adjust margins
const autoMargin =
instance.isServer && !instance.font
? false
: instance.config().autoMargin !== false;

// Create the Plot
instance.plotObject = autoMargin
? PlotAutoMargin(plotOptions, {}, instance.font)
: Plot.plot(plotOptions);

instance.plotObject.setAttribute("xmlns", "http://www.w3.org/2000/svg");
let wrapper: HTMLElement | SVGElement | null = null;
instance.plotObject.classList.add("plot-object");

// Find the parent of the existing chart element
const parentElement = instance.chartElement?.parentElement;
// Replace existing content if there's a parent (for interactions)
if (parentElement) {
const existingWrapper = parentElement.querySelector(`#${instance.id}`);
if (existingWrapper) {
wrapper = existingWrapper as HTMLElement | SVGElement;
// Clear the wrapper if we're updating the legend
if (newLegend) {
wrapper.innerHTML = "";
} else {
// Otherwise just remove the plot
wrapper.removeChild(wrapper.lastChild!);
}
}
} else {
wrapper = instance.document.createElement("div");
wrapper.id = instance.id;
// Ensure the chart container exists
const container =
instance.chartContainer || instance.document.createElement("div");
if (!instance.chartContainer) {
container.id = instance.id;
instance.chartContainer = container;
}

// Clear existing content if necessary
if (newLegend) container.innerHTML = "";

// Add or update the legend
if (instance.hasLegend && newLegend) {
let legend: HTMLDivElement;
const div = instance.document.createElement("div");
const legendContainer =
container.querySelector(".legend-container") ||
container.appendChild(instance.document.createElement("div"));

if (instance.legendType === "categorical") {
legend = await legendCategorical(instance);
} else {
legend = await legendContinuous(instance);
}
div.appendChild(legend);
if (wrapper) wrapper?.appendChild(div);
legendContainer.className = "legend-container";
legendContainer.innerHTML = ""; // Clear old content
const legend =
instance.legendType === "categorical"
? await legendCategorical(instance)
: await legendContinuous(instance);
legendContainer.appendChild(legend);
}
if (wrapper) {
wrapper.appendChild(instance.plotObject);
instance.chartElement = wrapper as HTMLElement; // track this for re-rendering via interactivity

// Replace or append the plot
const existingPlot = container.querySelector(".plot-object");
if (existingPlot) {
container.replaceChild(instance.plotObject, existingPlot);
} else {
container.appendChild(instance.plotObject);
}
return wrapper ?? null;

return container;
}

0 comments on commit 94b6632

Please sign in to comment.