Skip to content

Commit 2ea0451

Browse files
committed
make plot controllable programmatically
1 parent 2087b8e commit 2ea0451

File tree

12 files changed

+673
-130
lines changed

12 files changed

+673
-130
lines changed

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ A React + Vite + TypeScript app demonstrating an Observable Plot scatter plot wi
88
- 🖱️ Interactive brush selection – drag to select data points
99
- 🎨 Live visual feedback – selected points highlighted, non-selected dimmed
1010
- 📈 Real-time counter showing number of selected points
11+
- 🎮 **Controlled selection** – programmatically set selection from props
12+
- 🔄 Bidirectional data flow – selection updates flow both ways
1113

1214
## Tech Stack
1315

@@ -27,10 +29,25 @@ Open your browser and navigate to the local dev server (typically `http://localh
2729

2830
## Usage
2931

32+
### Interactive Selection
33+
3034
- **Drag** within the chart area to create a rectangular selection
3135
- **Selected points** remain fully visible; non-selected points are dimmed
3236
- The **"Selected: N"** counter updates in real-time
3337
- **Click outside** the selection or brush over empty area to clear
38+
- **Press Escape** to clear the selection
39+
40+
### Controlled Selection (New!)
41+
42+
The ScatterPlot component now supports controlled selection, allowing you to programmatically set the selection from your code. See [CONTROLLED_SELECTION.md](./CONTROLLED_SELECTION.md) for detailed documentation.
43+
44+
```tsx
45+
<ScatterPlot
46+
data={myData}
47+
selection={selection} // Control selection via prop
48+
onSelectionChange={(points, sel) => setSelection(sel)}
49+
/>
50+
```
3451

3552
## Implementation Notes
3653

@@ -40,7 +57,17 @@ The brush is appended inside the **same parent group** as the circle elements, e
4057

4158
## Project Structure
4259

43-
- `src/components/PlotScatterBrush.tsx` – Main chart component with brush logic
60+
- `src/components/ScatterPlot/` – Modular scatter plot component library
61+
- `ScatterPlot.tsx` – Main component with controlled selection support
62+
- `brush.ts` – Brush creation and programmatic control utilities
63+
- `plot.ts` – Observable Plot configuration and helpers
64+
- `types.ts` – TypeScript type definitions
65+
- `examples.tsx` – Usage examples including controlled selection demo
66+
- `legends.ts` – Legend creation utilities
67+
- `utils.ts` – Helper functions
68+
- `src/components/PlotScatterBrush.tsx` – Original demo component
4469
- `src/App.tsx` – Page layout
4570
- `src/main.tsx` – React entry point
46-
- `src/styles.css` – Styles including `.hidden` class for dimming
71+
- `src/styles.css` – Application styles
72+
- `CONTROLLED_SELECTION.md` – Documentation for controlled selection feature
73+
- `IMPLEMENTATION_SUMMARY.md` – Summary of recent implementation changes

src/App.tsx

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
1-
import { PlotScatterBrush } from "./components/PlotScatterBrush";
1+
import { useMemo, useState } from "react";
2+
import {
3+
ScatterPlot,
4+
BrushSelection,
5+
Point,
6+
generateRandomData,
7+
} from "./components/ScatterPlot";
28

39
export default function App() {
10+
const data = useMemo(() => generateRandomData(800), []);
11+
const [selection, setSelection] = useState<BrushSelection | null>(null);
12+
const [selectedPoints, setSelectedPoints] = useState<Point[]>([]);
13+
14+
const handleSelectionChange = (
15+
points: Point[],
16+
newSelection: BrushSelection | null
17+
) => {
18+
setSelectedPoints(points);
19+
setSelection(newSelection);
20+
};
21+
22+
// Programmatic selection using data coordinates
23+
const selectCenterRegion = () => {
24+
// Select center region (data coordinates around mean ~50)
25+
setSelection({ x0: 40, y0: 40, x1: 60, y1: 60 });
26+
};
27+
28+
const selectLowRegion = () => {
29+
// Select lower-left region
30+
setSelection({ x0: 20, y0: 20, x1: 45, y1: 45 });
31+
};
32+
33+
const clearSelection = () => {
34+
setSelection(null);
35+
};
36+
437
return (
538
<div className="app">
639
<h1>Observable Plot: 4D Scatter with Brush</h1>
@@ -12,9 +45,52 @@ export default function App() {
1245
<p>
1346
<em>Drag to select points. Non-selected points become translucent.</em>
1447
</p>
48+
49+
<div style={{ marginBottom: 16 }}>
50+
<strong>Programmatic Selection:</strong>
51+
<div style={{ display: "flex", gap: 8, marginTop: 8 }}>
52+
<button onClick={selectCenterRegion}>
53+
Select Center (x:40-60, y:40-60)
54+
</button>
55+
<button onClick={selectLowRegion}>
56+
Select Low Region (x:20-45, y:20-45)
57+
</button>
58+
<button onClick={clearSelection}>Clear Selection</button>
59+
</div>
60+
</div>
61+
1562
<div className="chart-container">
16-
<PlotScatterBrush />
63+
<ScatterPlot
64+
data={data}
65+
selection={selection}
66+
onSelectionChange={handleSelectionChange}
67+
enableBrush={true}
68+
/>
1769
</div>
70+
71+
{selectedPoints.length > 0 && (
72+
<div
73+
style={{
74+
marginTop: 16,
75+
padding: 16,
76+
background: "#f0f7ff",
77+
borderRadius: 8,
78+
border: "1px solid #4a90e2",
79+
}}
80+
>
81+
<h3 style={{ marginTop: 0 }}>Selection Info</h3>
82+
<p>
83+
<strong>Selected Points:</strong> {selectedPoints.length}
84+
</p>
85+
{selection && (
86+
<p>
87+
<strong>Data Coordinates:</strong> ({selection.x0.toFixed(2)},{" "}
88+
{selection.y0.toFixed(2)}) to ({selection.x1.toFixed(2)},{" "}
89+
{selection.y1.toFixed(2)})
90+
</p>
91+
)}
92+
</div>
93+
)}
1894
</div>
1995
);
2096
}

0 commit comments

Comments
 (0)