Skip to content

Commit 2bd9ac8

Browse files
authored
feat: rectangle and brush type lasso selection (#212)
* feat: add `brush` and `rectangle` lasso types Fix #186 * feat: add tweakpane to examples for better customizations * feat: allow removing selected points Fix #105 * refactor: rename `keyMap` to `actionKeyMap` To allow triggering multiple actions with the same key. * test: rectangle and brush lasso selections * docs: update changelog * docs: link ticket
1 parent 3b3f28e commit 2bd9ac8

31 files changed

Lines changed: 1148 additions & 1176 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## 1.13.0
22

3+
- Feat: add support for two new lasso types: `'rectangle'` and `'brush'`. The lasso type can be changed via `lassoType`. Additionally, for the brush lasso, you can adjust the brush size via `lassoBrushSize`. The default lasso type is `'freeform'`. ([#186](https://github.com/flekschas/regl-scatterplot/issues/186))
4+
- Feat: replace `keyMap` with `actionKeyMap` to allow triggering multiple actions with the same modifier key.
5+
- Feat: add `'remove'` key action to allow removing selecting points. By default, to rgitemove selected points hold down `ALT` and then lasso around selected points. ([#105](https://github.com/flekschas/regl-scatterplot/issues/105))
36
- Feat: expose `renderPointsAsSquares` and `disableAlphaBlending` to allow finer control over performance increasing settings ([#206](https://github.com/flekschas/regl-scatterplot/issues/206))
47

58
## 1.12.1

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,7 @@ can be read and written via [`scatterplot.get()`](#scatterplot.get) and [`scatte
793793
| pointConnectionMaxIntPointsPerSegment | int | `100` | | `true` | `false` |
794794
| pointConnectionTolerance | float | `0.002` | | `true` | `false` |
795795
| pointScaleMode | string | `'asinh'` | `'asinh'`, `'linear'`, or `'constant'` | `true` | `false` |
796+
| lassoType | string | `'freeform'` | `'freeform'`, `'rectangle'`, or `'brush'` | `true` | `false` |
796797
| lassoColor | quadruple | rgba(0, 0.667, 1, 1) | hex, rgb, rgba | `true` | `false` |
797798
| lassoLineWidth | float | 2 | >= 1 | `true` | `false` |
798799
| lassoMinDelay | int | 15 | >= 0 | `true` | `false` |
@@ -807,11 +808,12 @@ can be read and written via [`scatterplot.get()`](#scatterplot.get) and [`scatte
807808
| lassoLongPressAfterEffectTime | int | `500` | | `true` | `false` |
808809
| lassoLongPressEffectDelay | int | `100` | | `true` | `false` |
809810
| lassoLongPressRevertEffectTime | int | `250` | | `true` | `false` |
811+
| lassoBrushSize | int | `24` | | `true` | `false` |
810812
| showReticle | boolean | `false` | `true` or `false` | `true` | `false` |
811813
| reticleColor | quadruple | rgba(1, 1, 1, .5) | hex, rgb, rgba | `true` | `false` |
812814
| xScale | function | `null` | must follow the D3 scale API | `true` | `true` |
813815
| yScale | function | `null` | must follow the D3 scale API | `true` | `true` |
814-
| keyMap | object | `{ alt: 'rotate', shift: 'lasso' }` | See the notes below | `true` | `false` |
816+
| actionKeyMap | object | `{ remove: 'alt': rotate: 'alt', merge: 'cmd', lasso: 'shift' }` | See the notes below | `true` | `false` |
815817
| mouseMode | string | `'panZoom'` | `'panZoom'`, `'lasso'`, or `'rotate'` | `true` | `false` |
816818
| performanceMode | boolean | `false` | can only be set during initialization! | `true` | `false` |
817819
| gamma | float | `1` | to control the opacity blending | `true` | `false` |
@@ -911,15 +913,16 @@ You don't like the look of the lasso initiator? No problem. Simple get the DOM
911913
element via `scatterplot.get('lassoInitiatorElement')` and adjust the style
912914
via JavaScript. E.g.: `scatterplot.get('lassoInitiatorElement').style.background = 'green'`.
913915

914-
<a name="property-keymap" href="#property-keymap">#</a> <b>KeyMap:</b>
916+
<a name="property-keymap" href="#property-keymap">#</a> <b>ActionKeyMap:</b>
915917

916-
The `keyMap` property is an object defining which actions are enabled when
917-
holding down which modifier key. E.g.: `{ shift: 'lasso' }`. Acceptable
918-
modifier keys are `alt`, `cmd`, `ctrl`, `meta`, `shift`. Acceptable actions
919-
are `lasso`, `rotate`, and `merge` (for selecting multiple items by merging a series of lasso or click selections).
918+
The `actionKeyMap` property is an object defining which actions are enabled when
919+
holding down which modifier key. E.g.: `{ lasso: 'shift' }`. Acceptable actions
920+
are `lasso`, `rotate`, `merge` (for selecting multiple items by merging a series
921+
of lasso or click selections), and `remove` (for removing selected points).
922+
Acceptable modifier keys are `alt`, `cmd`, `ctrl`, `meta`, `shift`.
920923

921-
You can also use the `keyMap` option to disable the lasso selection and rotation
922-
by setting `keyMap` to an empty object.
924+
You can also use the `actionKeyMap` option to disable the lasso selection and
925+
rotation by setting `actionKeyMap` to an empty object.
923926

924927
<a name="property-examples" href="#property-examples">#</a> <b>Examples:</b>
925928

example/annotations.js

Lines changed: 7 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
1-
/* eslint no-console: 0 */
2-
31
import createScatterplot from '../src';
4-
import { saveAsPng, checkSupport } from './utils';
2+
import createMenu from './menu';
3+
import { checkSupport } from './utils';
54

65
const canvas = document.querySelector('#canvas');
7-
const numPointsEl = document.querySelector('#num-points');
8-
const numPointsValEl = document.querySelector('#num-points-value');
9-
const pointSizeEl = document.querySelector('#point-size');
10-
const pointSizeValEl = document.querySelector('#point-size-value');
11-
const opacityEl = document.querySelector('#opacity');
12-
const opacityValEl = document.querySelector('#opacity-value');
13-
const clickLassoInitiatorEl = document.querySelector('#click-lasso-initiator');
14-
const resetEl = document.querySelector('#reset');
15-
const exportEl = document.querySelector('#export');
16-
const exampleEl = document.querySelector('#example-annotations');
17-
18-
exampleEl.setAttribute('class', 'active');
19-
exampleEl.removeAttribute('href');
206

217
let points = { x: [], y: [], z: [], w: [] };
228
let numPoints = 100000;
@@ -60,15 +46,14 @@ const scatterplot = createScatterplot({
6046
lassoMinDelay,
6147
lassoMinDist,
6248
pointSize,
49+
opacity,
6350
showReticle,
6451
reticleColor,
6552
lassoOnLongPress: true,
6653
});
6754

6855
checkSupport(scatterplot);
6956

70-
exportEl.addEventListener('click', () => saveAsPng(scatterplot));
71-
7257
console.log(`Scatterplot v${scatterplot.get('version')}`);
7358

7459
scatterplot.subscribe('select', selectHandler);
@@ -122,74 +107,20 @@ const generatePoints = (l) => {
122107
return { x, y, z, w };
123108
};
124109

125-
const setNumPoint = (newNumPoints) => {
126-
numPoints = newNumPoints;
127-
numPointsEl.value = numPoints;
128-
numPointsValEl.innerHTML = numPoints;
129-
points = generatePoints(numPoints);
110+
const setNumPoints = (newNumPoints) => {
111+
points = generatePoints(newNumPoints);
130112
scatterplot.draw(points);
131113
};
132114

133-
const numPointsInputHandler = (event) => {
134-
numPointsValEl.innerHTML = `${+event.target
135-
.value} <em>release to redraw</em>`;
136-
};
137-
138-
numPointsEl.addEventListener('input', numPointsInputHandler);
139-
140-
const numPointsChangeHandler = (event) => setNumPoint(+event.target.value);
141-
142-
numPointsEl.addEventListener('change', numPointsChangeHandler);
143-
144-
const setPointSize = (newPointSize) => {
145-
pointSize = newPointSize;
146-
pointSizeEl.value = pointSize;
147-
pointSizeValEl.innerHTML = pointSize;
148-
scatterplot.set({ pointSize });
149-
};
150-
151-
const pointSizeInputHandler = (event) => setPointSize(+event.target.value);
152-
153-
pointSizeEl.addEventListener('input', pointSizeInputHandler);
154-
155-
const setOpacity = (newOpacity) => {
156-
opacity = newOpacity;
157-
opacityEl.value = opacity;
158-
opacityValEl.innerHTML = opacity;
159-
scatterplot.set({ opacity });
160-
};
161-
162-
const opacityInputHandler = (event) => setOpacity(+event.target.value);
163-
164-
opacityEl.addEventListener('input', opacityInputHandler);
165-
166-
const clickLassoInitiatorChangeHandler = (event) => {
167-
scatterplot.set({
168-
lassoInitiator: event.target.checked,
169-
});
170-
};
171-
172-
clickLassoInitiatorEl.addEventListener(
173-
'change',
174-
clickLassoInitiatorChangeHandler
175-
);
176-
clickLassoInitiatorEl.checked = scatterplot.get('lassoInitiator');
177-
178-
const resetClickHandler = () => {
179-
scatterplot.reset();
180-
};
181-
182-
resetEl.addEventListener('click', resetClickHandler);
115+
createMenu({ scatterplot, setNumPoints });
183116

184117
const colorsCat = ['#3a78aa', '#aa3a99'];
185118
scatterplot.set({ colorBy: 'category', pointColor: colorsCat });
186119

187120
const colorsScale = ['#009E73', '#CC79A7', '#56B4E9', '#F0E442'];
188121
scatterplot.set({ colorBy: 'z', pointColor: colorsScale });
189122

190-
setPointSize(pointSize);
191-
setOpacity(opacity);
192-
setNumPoint(numPoints);
123+
setNumPoints(numPoints);
193124

194125
scatterplot.drawAnnotations([
195126
{ x: 0, lineColor: [1, 1, 1, 0.1], lineWidth: 1 },

example/axes.js

Lines changed: 7 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,14 @@
1-
/* eslint no-console: 0 */
2-
31
import { axisBottom, axisRight } from 'd3-axis';
42
import { scaleLinear } from 'd3-scale';
53
import { select } from 'd3-selection';
64

75
import createScatterplot from '../src';
8-
import { saveAsPng, checkSupport } from './utils';
6+
import createMenu from './menu';
7+
import { checkSupport } from './utils';
98

109
const parentWrapper = document.querySelector('#parent-wrapper');
1110
const canvasWrapper = document.querySelector('#canvas-wrapper');
1211
const canvas = document.querySelector('#canvas');
13-
const numPointsEl = document.querySelector('#num-points');
14-
const numPointsValEl = document.querySelector('#num-points-value');
15-
const pointSizeEl = document.querySelector('#point-size');
16-
const pointSizeValEl = document.querySelector('#point-size-value');
17-
const opacityEl = document.querySelector('#opacity');
18-
const opacityValEl = document.querySelector('#opacity-value');
19-
const clickLassoInitiatorEl = document.querySelector('#click-lasso-initiator');
20-
const resetEl = document.querySelector('#reset');
21-
const exportEl = document.querySelector('#export');
22-
const exampleBg = document.querySelector('#example-axes');
23-
24-
exampleBg.setAttribute('class', 'active');
25-
exampleBg.removeAttribute('href');
2612

2713
const xDomain = [0, 42];
2814
const yDomain = [0, 4.2];
@@ -80,6 +66,7 @@ const deselectHandler = () => {
8066
const scatterplot = createScatterplot({
8167
canvas,
8268
pointSize,
69+
opacity,
8370
xScale,
8471
yScale,
8572
showReticle: true,
@@ -88,8 +75,6 @@ const scatterplot = createScatterplot({
8875

8976
checkSupport(scatterplot);
9077

91-
exportEl.addEventListener('click', () => saveAsPng(scatterplot));
92-
9378
console.log(`Scatterplot v${scatterplot.get('version')}`);
9479

9580
scatterplot.subscribe('select', selectHandler);
@@ -130,65 +115,12 @@ const generatePoints = (num) =>
130115
Math.random(), // value
131116
]);
132117

133-
const setNumPoint = (newNumPoints) => {
134-
numPoints = newNumPoints;
135-
numPointsEl.value = numPoints;
136-
numPointsValEl.innerHTML = numPoints;
137-
points = generatePoints(numPoints);
138-
118+
const setNumPoints = (newNumPoints) => {
119+
points = generatePoints(newNumPoints);
139120
scatterplot.draw(points);
140121
};
141122

142-
const numPointsInputHandler = (event) => {
143-
numPointsValEl.innerHTML = `${+event.target
144-
.value} <em>release to redraw</em>`;
145-
};
146-
147-
numPointsEl.addEventListener('input', numPointsInputHandler);
148-
149-
const numPointsChangeHandler = (event) => setNumPoint(+event.target.value);
150-
151-
numPointsEl.addEventListener('change', numPointsChangeHandler);
152-
153-
const setPointSize = (newPointSize) => {
154-
pointSize = newPointSize;
155-
pointSizeEl.value = pointSize;
156-
pointSizeValEl.innerHTML = pointSize;
157-
scatterplot.set({ pointSize });
158-
};
159-
160-
const pointSizeInputHandler = (event) => setPointSize(+event.target.value);
161-
162-
pointSizeEl.addEventListener('input', pointSizeInputHandler);
163-
164-
const setOpacity = (newOpacity) => {
165-
opacity = newOpacity;
166-
opacityEl.value = opacity;
167-
opacityValEl.innerHTML = opacity;
168-
scatterplot.set({ opacity });
169-
};
170-
171-
const opacityInputHandler = (event) => setOpacity(+event.target.value);
172-
173-
opacityEl.addEventListener('input', opacityInputHandler);
174-
175-
const clickLassoInitiatorChangeHandler = (event) => {
176-
scatterplot.set({
177-
lassoInitiator: event.target.checked,
178-
});
179-
};
180-
181-
clickLassoInitiatorEl.addEventListener(
182-
'change',
183-
clickLassoInitiatorChangeHandler
184-
);
185-
clickLassoInitiatorEl.checked = scatterplot.get('lassoInitiator');
186-
187-
const resetClickHandler = () => {
188-
scatterplot.reset();
189-
};
190-
191-
resetEl.addEventListener('click', resetClickHandler);
123+
createMenu({ scatterplot, setNumPoints });
192124

193125
scatterplot.set({
194126
colorBy: 'category',
@@ -203,6 +135,4 @@ scatterplot.set({
203135
],
204136
});
205137

206-
setPointSize(pointSize);
207-
setOpacity(opacity);
208-
setNumPoint(numPoints);
138+
setNumPoints(numPoints);

0 commit comments

Comments
 (0)