Skip to content

Commit 014fa99

Browse files
committed
Render anchor point when moving a pixel sized shape [2038]
- Reason: resizing a pixel sized shape is changing the distance to its anchor point. The anchor point not being visible caused misleading perceptions in case of auto-ranged axis because the auto-range algorithm takes both anchor point and pixel coordinates of shape into account. - Even works for `path` shapes and shapes where only one dimension is pixel sized.
1 parent 116d53f commit 014fa99

File tree

3 files changed

+75
-19
lines changed

3 files changed

+75
-19
lines changed

src/components/shapes/calc_autorange.js

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) {
6262

6363
if(sizeMode === 'pixel') {
6464
var coords = path ?
65-
extractPathCoords(path, isYAxis ? constants.paramIsY : constants.paramIsX) :
65+
helpers.extractPathCoords(path, isYAxis ? constants.paramIsY : constants.paramIsX) :
6666
[v0, v1];
6767
var maxValue = Lib.aggNums(Math.max, null, coords),
6868
minValue = Lib.aggNums(Math.min, null, coords),
@@ -79,23 +79,6 @@ function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) {
7979
}
8080
}
8181

82-
function extractPathCoords(path, paramsToUse) {
83-
var extractedCoordinates = [];
84-
85-
var segments = path.match(constants.segmentRE);
86-
segments.forEach(function(segment) {
87-
var relevantParamIdx = paramsToUse[segment.charAt(0)].drawn;
88-
if(relevantParamIdx === undefined) return;
89-
90-
var params = segment.substr(1).match(constants.paramRE);
91-
if(!params || params.length < relevantParamIdx) return;
92-
93-
extractedCoordinates.push(params[relevantParamIdx]);
94-
});
95-
96-
return extractedCoordinates;
97-
}
98-
9982
function shapeBounds(ax, v0, v1, path, paramsToUse) {
10083
var convertVal = (ax.type === 'category') ? ax.r2c : ax.d2c;
10184

src/components/shapes/draw.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) {
152152
element: sensoryElement.node(),
153153
gd: gd,
154154
prepFn: startDrag,
155-
doneFn: endDrag
155+
doneFn: endDrag,
156+
clickFn: abortDrag
156157
},
157158
dragMode;
158159

@@ -295,18 +296,24 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) {
295296

296297
// setup dragMode and the corresponding handler
297298
updateDragMode(evt);
299+
renderVisualCues(shapeLayer, shapeOptions);
298300
deactivateClipPathTemporarily(shapePath, shapeOptions, gd);
299301
dragOptions.moveFn = (dragMode === 'move') ? moveShape : resizeShape;
300302
}
301303

302304
function endDrag() {
303305
setCursor(shapePath);
306+
removeVisualCues(shapeLayer);
304307

305308
// Don't rely on clipPath being activated during re-layout
306309
setClipPath(shapePath, gd, shapeOptions);
307310
Registry.call('relayout', gd, update);
308311
}
309312

313+
function abortDrag() {
314+
removeVisualCues(shapeLayer);
315+
}
316+
310317
function moveShape(dx, dy) {
311318
if(shapeOptions.type === 'path') {
312319
var noOp = function(coord) { return coord; },
@@ -347,6 +354,7 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) {
347354
}
348355

349356
shapePath.attr('d', getPathString(gd, shapeOptions));
357+
renderVisualCues(shapeLayer, shapeOptions);
350358
}
351359

352360
function resizeShape(dx, dy) {
@@ -411,6 +419,52 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) {
411419
}
412420

413421
shapePath.attr('d', getPathString(gd, shapeOptions));
422+
renderVisualCues(shapeLayer, shapeOptions);
423+
}
424+
425+
function renderVisualCues(shapeLayer, shapeOptions) {
426+
if(xPixelSized || yPixelSized) {
427+
renderAnchor();
428+
}
429+
430+
function renderAnchor() {
431+
var isNotPath = shapeOptions.type !== 'path';
432+
433+
// d3 join with dummy data to satisfy d3 data-binding
434+
var visualCues = shapeLayer.selectAll('.visual-cue').data([0]);
435+
436+
// Enter
437+
visualCues.enter()
438+
.append('path')
439+
.attr({
440+
'fill': '#fff',
441+
'fill-rule': 'evenodd',
442+
'stroke': '#000',
443+
'stroke-width': 1
444+
})
445+
.classed('visual-cue', true);
446+
447+
// Update
448+
var anchorX = x2p(xPixelSized ?
449+
shapeOptions.xanchor :
450+
isNotPath ?
451+
shapeOptions.x0 :
452+
helpers.extractPathCoords(shapeOptions.path, constants.paramIsX)[0]);
453+
var anchorY = y2p(yPixelSized ?
454+
shapeOptions.yanchor :
455+
isNotPath ?
456+
shapeOptions.y0 :
457+
helpers.extractPathCoords(shapeOptions.path, constants.paramIsY)[0]);
458+
459+
var crossHairPath = 'M' + (anchorX - 1) + ',' + (anchorY - 1) +
460+
'l-8,0 l0,-2 l8,0 l0,-8 l2,0 l0,8 l8,0 l0,2 l-8,0 l0,8 l-2,0 Z';
461+
462+
visualCues.attr('d', crossHairPath);
463+
}
464+
}
465+
466+
function removeVisualCues(shapeLayer) {
467+
shapeLayer.selectAll('.visual-cue').remove();
414468
}
415469

416470
function deactivateClipPathTemporarily(shapePath, shapeOptions, gd) {

src/components/shapes/helpers.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
'use strict';
1111

12+
var constants = require('./constants');
13+
1214
// special position conversion functions... category axis positions can't be
1315
// specified by their data values, because they don't make a continuous mapping.
1416
// so these have to be specified in terms of the category serial numbers,
@@ -37,6 +39,23 @@ exports.encodeDate = function(convertToDate) {
3739
return function(v) { return convertToDate(v).replace(' ', '_'); };
3840
};
3941

42+
exports.extractPathCoords = function(path, paramsToUse) {
43+
var extractedCoordinates = [];
44+
45+
var segments = path.match(constants.segmentRE);
46+
segments.forEach(function(segment) {
47+
var relevantParamIdx = paramsToUse[segment.charAt(0)].drawn;
48+
if(relevantParamIdx === undefined) return;
49+
50+
var params = segment.substr(1).match(constants.paramRE);
51+
if(!params || params.length < relevantParamIdx) return;
52+
53+
extractedCoordinates.push(params[relevantParamIdx]);
54+
});
55+
56+
return extractedCoordinates;
57+
};
58+
4059
exports.getDataToPixel = function(gd, axis, isVertical) {
4160
var gs = gd._fullLayout._size,
4261
dataToPixel;

0 commit comments

Comments
 (0)