diff --git a/src/traces/parcoords/axisbrush.js b/src/traces/parcoords/axisbrush.js index e84c53c4438..b0add8c017e 100644 --- a/src/traces/parcoords/axisbrush.js +++ b/src/traces/parcoords/axisbrush.js @@ -25,37 +25,31 @@ function closeToCovering(v, vAdjacent) { return v * (1 - snapClose) + vAdjacent // so it's clear we're covering it // find the interval we're in, and snap to 1/4 the distance to the next // these two could be unified at a slight loss of readability / perf -function ordinalScaleSnapLo(a, v, existingRanges) { +function ordinalScaleSnap(isHigh, a, v, existingRanges) { if(overlappingExisting(v, existingRanges)) return v; - var aPrev = a[0]; - var aPrevPrev = aPrev; - for(var i = 1; i < a.length; i++) { - var aNext = a[i]; + var dir = isHigh ? -1 : 1; - // very close to the previous - snap down to it - if(v < closeToCovering(aPrev, aNext)) return snapOvershoot(aPrev, aPrevPrev); - if(v < aNext || i === a.length - 1) return snapOvershoot(aNext, aPrev); - - aPrevPrev = aPrev; - aPrev = aNext; + var first = 0; + var last = a.length - 1; + if(dir < 0) { + var tmp = first; + first = last; + last = tmp; } -} - -function ordinalScaleSnapHi(a, v, existingRanges) { - if(overlappingExisting(v, existingRanges)) return v; - var aPrev = a[a.length - 1]; - var aPrevPrev = aPrev; - for(var i = a.length - 2; i >= 0; i--) { - var aNext = a[i]; + var aHere = a[first]; + var aPrev = aHere; + for(var i = first; dir * i < dir * last; i += dir) { + var nextI = i + dir; + var aNext = a[nextI]; // very close to the previous - snap down to it - if(v > closeToCovering(aPrev, aNext)) return snapOvershoot(aPrev, aPrevPrev); - if(v > aNext || i === a.length - 1) return snapOvershoot(aNext, aPrev); + if(dir * v < dir * closeToCovering(aHere, aNext)) return snapOvershoot(aHere, aPrev); + if(dir * v < dir * aNext || nextI === last) return snapOvershoot(aNext, aHere); - aPrevPrev = aPrev; - aPrev = aNext; + aPrev = aHere; + aHere = aNext; } } @@ -333,8 +327,8 @@ function attachDragBehavior(selection) { var a = d.unitTickvals; if(a[a.length - 1] < a[0]) a.reverse(); s.newExtent = [ - ordinalScaleSnapLo(a, s.newExtent[0], s.stayingIntervals), - ordinalScaleSnapHi(a, s.newExtent[1], s.stayingIntervals) + ordinalScaleSnap(0, a, s.newExtent[0], s.stayingIntervals), + ordinalScaleSnap(1, a, s.newExtent[1], s.stayingIntervals) ]; var hasNewExtent = s.newExtent[1] > s.newExtent[0]; s.extent = s.stayingIntervals.concat(hasNewExtent ? [s.newExtent] : []); @@ -505,8 +499,8 @@ function cleanRanges(ranges, dimension) { var sortedTickVals = dimension.tickvals.slice().sort(sortAsc); ranges = ranges.map(function(ri) { var rSnapped = [ - ordinalScaleSnapLo(sortedTickVals, ri[0], []), - ordinalScaleSnapHi(sortedTickVals, ri[1], []) + ordinalScaleSnap(0, sortedTickVals, ri[0], []), + ordinalScaleSnap(1, sortedTickVals, ri[1], []) ]; if(rSnapped[1] > rSnapped[0]) return rSnapped; }) diff --git a/test/image/baselines/gl2d_parcoords_select_first_last_enum.png b/test/image/baselines/gl2d_parcoords_select_first_last_enum.png new file mode 100644 index 00000000000..b0cf3961415 Binary files /dev/null and b/test/image/baselines/gl2d_parcoords_select_first_last_enum.png differ diff --git a/test/image/mocks/gl2d_parcoords_select_first_last_enum.json b/test/image/mocks/gl2d_parcoords_select_first_last_enum.json new file mode 100644 index 00000000000..f48818b86f0 --- /dev/null +++ b/test/image/mocks/gl2d_parcoords_select_first_last_enum.json @@ -0,0 +1,85 @@ +{ + "data": [ + { + "type": "parcoords", + "line": { + "colorscale": "Portland", + "showscale": true, + "reversescale": true, + "color": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + "dimensions": [ + { + "label": "A", + "values": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "tickvals": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "constraintrange": [ + [ + 1, + 1 + ], + [ + 3, + 3 + ], + [ + 5, + 6 + ], + [ + 8, + 8 + ] + ] + }, + { + "label": "B", + "values": [ + 1, + 2, + 3, + 5, + 8, + 13, + 22, + 35 + ] + } + ] + } + ], + "layout": { + "width": 400, + "height": 400, + "title": { + "text": "should select first and last enum" + } + } +}