Skip to content

Commit a7bd686

Browse files
committed
click to select an ordinal value
1 parent 0e75c5a commit a7bd686

File tree

2 files changed

+78
-15
lines changed

2 files changed

+78
-15
lines changed

src/traces/parcoords/axisbrush.js

+61-14
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,15 @@ function renderHighlight(root, tweenCallback) {
193193
styleHighlight(barToStyle);
194194
}
195195

196-
function getInterval(b, height, y) {
196+
function getInterval(b, d, y) {
197+
var height = d.height;
197198
var intervals = b.filter.getConsolidated();
198199
var pixIntervals = unitToPx(intervals, height);
199200
var hoveredInterval = NaN;
200201
var previousInterval = NaN;
201202
var nextInterval = NaN;
202-
for(var i = 0; i <= pixIntervals.length; i++) {
203+
var i;
204+
for(i = 0; i <= pixIntervals.length; i++) {
203205
var p = pixIntervals[i];
204206
if(p && p[0] <= y && y <= p[1]) {
205207
// over a bar
@@ -226,15 +228,34 @@ function getInterval(b, height, y) {
226228
}
227229
}
228230

231+
var foundInterval = !isNaN(closestInterval);
232+
229233
var fPix = pixIntervals[closestInterval];
230234

231-
return {
232-
interval: isNaN(closestInterval) ? null : intervals[closestInterval], // activated interval in domain terms
233-
intervalPix: isNaN(closestInterval) ? null : fPix, // activated interval in pixel terms
235+
var out = {
236+
interval: foundInterval ? intervals[closestInterval] : null, // activated interval in domain terms
237+
intervalPix: foundInterval ? fPix : null, // activated interval in pixel terms
234238
n: north(fPix, y), // do we hover over the northern resize hotspot
235239
s: south(fPix, y), // do we hover over the northern resize hotspot
236240
m: middle(fPix, y) // or over the bar section itself?
237241
};
242+
243+
if(d.ordinal && (!(out.n || out.s || out.m) || !filterActive(b))) {
244+
var a = d.ordinalScale.range().map(d.paddedUnitScale);
245+
var unitLocation = d.unitScaleInOrder.invert(y);
246+
for(i = 0; i < a.length; i++) {
247+
var rangei = [
248+
a[Math.max(i - 1, 0)] * 0.25 + a[i] * 0.75,
249+
a[Math.min(i + 1, a.length - 1)] * 0.25 + a[i] * 0.75
250+
];
251+
if(unitLocation >= rangei[0] && unitLocation <= rangei[1]) {
252+
out.clickableOrdinalRange = rangei;
253+
break;
254+
}
255+
}
256+
}
257+
258+
return out;
238259
}
239260

240261
function attachDragBehavior(selection) {
@@ -249,9 +270,16 @@ function attachDragBehavior(selection) {
249270
return;
250271
}
251272
var y = d.height - d3.mouse(this)[1] - 2 * c.verticalPadding;
252-
var interval = getInterval(b, d.height, y);
273+
var interval = getInterval(b, d, y);
274+
var cursor = 'crosshair';
275+
if(interval.clickableOrdinalRange) cursor = 'pointer';
276+
else if(filterActive(b)) {
277+
if(interval.n) cursor = 'n-resize';
278+
else if(interval.s) cursor = 's-resize';
279+
else if(interval.m) cursor = 'ns-resize';
280+
}
253281
d3.select(document.body)
254-
.style('cursor', interval.n ? 'n-resize' : interval.s ? 's-resize' : !interval.m ? 'crosshair' : filterActive(b) ? 'ns-resize' : 'crosshair');
282+
.style('cursor', cursor);
255283
})
256284
.on('mouseleave', function(d) {
257285
if(d.parent.inBrushDrag) {
@@ -266,7 +294,7 @@ function attachDragBehavior(selection) {
266294
var y = d.height - d3.mouse(this)[1] - 2 * c.verticalPadding;
267295
var unitLocation = d.unitScaleInOrder.invert(y);
268296
var b = d.brush;
269-
var intData = getInterval(b, d.height, y);
297+
var intData = getInterval(b, d, y);
270298
var unitRange = intData.interval;
271299
var pixelRange = unitRange.map(d.unitScaleInOrder);
272300
var s = b.svgBrush;
@@ -276,12 +304,13 @@ function attachDragBehavior(selection) {
276304
s.grabPoint = d.unitScaleInOrder(unitLocation) - pixelRange[0] - c.verticalPadding;
277305
s.barLength = pixelRange[1] - pixelRange[0];
278306
s.grabbingBar = active && intData.m && unitRange;
307+
s.clickableOrdinalRange = intData.clickableOrdinalRange;
279308
s.stayingIntervals = !d.multiselect ? [] :
280309
barInteraction ?
281310
b.filter.get().filter(differentInterval(unitRange)) :
282311
b.filter.get(); // keep all preexisting bars if interaction wasn't a barInteraction
283-
var grabbingBarNorth = intData.n;
284-
var grabbingBarSouth = intData.s;
312+
var grabbingBarNorth = active && intData.n;
313+
var grabbingBarSouth = active && intData.s;
285314
var newBrushing = !s.grabbingBar && !grabbingBarNorth && !grabbingBarSouth;
286315
s.startExtent = newBrushing ? unitLocation : unitRange[grabbingBarSouth ? 1 : 0];
287316
d.parent.inBrushDrag = true;
@@ -333,8 +362,17 @@ function attachDragBehavior(selection) {
333362
d.parent.inBrushDrag = false;
334363
clearCursor(); // instead of clearing, a nicer thing would be to set it according to current location
335364
if(!s.wasDragged) { // a click+release on the same spot (ie. w/o dragging) means a bar or full reset
336-
s.wasDragged = undefined; // logic-wise unneded, just shows `wasDragged` has no longer a meaning
337-
if(grabbingBar) {
365+
s.wasDragged = undefined; // logic-wise unneeded, just shows `wasDragged` has no longer a meaning
366+
if(s.clickableOrdinalRange) {
367+
if(brush.filterSpecified && d.multiselect) {
368+
s.extent.push(s.clickableOrdinalRange);
369+
}
370+
else {
371+
s.extent = [s.clickableOrdinalRange];
372+
brush.filterSpecified = true;
373+
}
374+
}
375+
else if(grabbingBar) {
338376
s.extent = s.stayingIntervals;
339377
if(s.extent.length === 0) {
340378
brushClear(brush);
@@ -361,12 +399,21 @@ function attachDragBehavior(selection) {
361399
ordinalScaleSnapLo(a, s.newExtent[0], s.stayingIntervals),
362400
ordinalScaleSnapHi(a, s.newExtent[1], s.stayingIntervals)
363401
];
364-
s.extent = s.stayingIntervals.concat(s.newExtent[1] > s.newExtent[0] ? [s.newExtent] : []);
402+
var hasNewExtent = s.newExtent[1] > s.newExtent[0];
403+
s.extent = s.stayingIntervals.concat(hasNewExtent ? [s.newExtent] : []);
365404
if(!s.extent.length) {
366405
brushClear(brush);
367406
}
368407
s.brushCallback(d);
369-
renderHighlight(this.parentNode, mergeIntervals); // merging intervals post the snap tween
408+
if(hasNewExtent) {
409+
// merging intervals post the snap tween
410+
renderHighlight(this.parentNode, mergeIntervals);
411+
}
412+
else {
413+
// if no new interval, don't animate, just redraw the highlight immediately
414+
mergeIntervals();
415+
renderHighlight(this.parentNode);
416+
}
370417
} else {
371418
mergeIntervals(); // merging intervals immediately
372419
}

test/jasmine/tests/parcoords_test.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -1175,12 +1175,28 @@ describe('@gl parcoords constraint interactions', function() {
11751175
expect(gd.data[0].dimensions[0].constraintrange).toBeCloseTo2DArray([[0.75, 2.25], [2.75, 4]]);
11761176

11771177
// clear the whole thing
1178-
click(105, 290);
1178+
click(105, 275);
11791179
})
11801180
.then(delay(snapDelay))
11811181
.then(function() {
11821182
checkDashCount(getDashArray(0), 0);
11831183
expect(gd.data[0].dimensions[0].constraintrange).toBeUndefined();
1184+
1185+
// click to select 1
1186+
click(105, 250);
1187+
})
1188+
.then(delay(noSnapDelay))
1189+
.then(function() {
1190+
checkDashCount(getDashArray(0), 1);
1191+
expect(gd.data[0].dimensions[0].constraintrange).toBeCloseToArray([0.75, 1.25]);
1192+
1193+
// click to select 4
1194+
click(105, 105);
1195+
})
1196+
.then(delay(noSnapDelay))
1197+
.then(function() {
1198+
checkDashCount(getDashArray(0), 2);
1199+
expect(gd.data[0].dimensions[0].constraintrange).toBeCloseTo2DArray([[0.75, 1.25], [3.75, 4]]);
11841200
})
11851201
.catch(failTest)
11861202
.then(done);

0 commit comments

Comments
 (0)