Skip to content

Commit

Permalink
Continuous legend reset fixes (#67)
Browse files Browse the repository at this point in the history
* Reset continuous legends

* Add initial brush

* Remove duplicate scale definition
  • Loading branch information
mkfreeman authored Nov 19, 2024
1 parent 0cb1bd8 commit 87114a2
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 19 deletions.
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export class DuckPlot {
});
this._newDataProps = false;
this._visibleSeries = []; // reset visible series
this._seriesDomain = []; // reset domain
return this._chartData;
}
if (!this._ddb || !this._table)
Expand All @@ -295,6 +296,7 @@ export class DuckPlot {
if (!this._mark) throw new Error("Mark type not set");
this._newDataProps = false;
this._visibleSeries = []; // reset visible series
this._seriesDomain = []; // reset domain
const columns = {
...(this._x.column ? { x: this._x.column } : {}),
...(this._y.column ? { y: this._y.column } : {}),
Expand Down Expand Up @@ -599,7 +601,8 @@ export class DuckPlot {
: (event) => {
this._seriesDomain = event;
this.render(false);
}
},
this._seriesDomain
);
}
div.appendChild(legend);
Expand Down
49 changes: 31 additions & 18 deletions src/legendContinuous.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import * as d3 from "d3";
import { PlotOptions } from "@observablehq/plot";
export function legendContinuous(
options: PlotOptions,
onBrush: null | ((domain: any[]) => void)
onBrush: null | ((domain: any[]) => void),
initialSelection?: any[] // Pass an initial selection range
): HTMLDivElement {
// Create a div container
const container = document.createElement("div");
container.style.position = "relative"; // Important for positioning elements correctly
container.style.position = "relative";
container.style.width = "300px";
// container.style.height = "100px";

const plotLegend = Plot.legend(options) as HTMLDivElement & Plot.Plot;
container.appendChild(plotLegend);
// Create an SVG element for the brush, inside the same div

if (onBrush !== null) {
const width = options.width || 240;
const height = options.height || 50;
Expand All @@ -22,36 +22,49 @@ export function legendContinuous(
.append("svg")
.attr("width", width)
.attr("height", height)
.style("position", "absolute") // Position it over the legend
.style("position", "absolute")
.style("top", "0px")
.style("left", "0px");

// Add a D3 brush on top of the legend
const brush = d3
.brushX()
.extent([
[0, 0],
[width, height],
]) // Adjust extent to match the container dimensions
])
.on("brush end", brushed);

svg.append("g").call(brush);
const brushGroup = svg.append("g").call(brush);

let isProgrammatic = false; // Flag to track programmatic updates

const scale = d3
.scaleLinear()
.domain(options?.color?.domain ?? [])
.range([0, width]);

// Function to handle brush events
function brushed(event: d3.D3BrushEvent<unknown>) {
if (isProgrammatic) {
isProgrammatic = false; // Reset the flag after programmatic change
return;
}

if (event.selection) {
// Gotta make a d3 linear scale
const scale = d3
.scaleLinear()
.domain(options?.color?.domain ?? [])
.range([0, width]).invert;
// const colorScale = plotLegend.scale.color;
const [x0, x1] = event.selection as number[];
if (onBrush) onBrush([scale(x0), scale(x1)]);
const [x0, x1] = event.selection as [number, number];
if (onBrush) onBrush([scale.invert(x0), scale.invert(x1)]);
} else {
if (onBrush) onBrush([]);
}
}

if (initialSelection && initialSelection.length === 2) {
const [x0, x1] = initialSelection.map(scale) as [number, number];

// Set the flag before programmatically moving the brush
isProgrammatic = true;
brushGroup.call(brush.move, [x0, x1]);
}
}

return container;
}

0 comments on commit 87114a2

Please sign in to comment.