Skip to content

Commit

Permalink
Merge pull request #7005 from my-tien/shape_shift
Browse files Browse the repository at this point in the history
property x0shift, x1shift, y0shift, y1shift for adjusting the shape coordinates
  • Loading branch information
archmoj committed Jul 16, 2024
2 parents ec283ee + 9ac970a commit e2653aa
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 56 deletions.
1 change: 1 addition & 0 deletions draftlogs/7005_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add property x0shift, x1shift, y0shift, y1shift for shapes referencing (multi-)category axes, with thanks to @my-tien for the contribution! [[#7005](https://github.com/plotly/plotly.js/pull/7005)]
50 changes: 48 additions & 2 deletions src/components/shapes/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,30 @@ module.exports = templatedArray('shape', {
'See `type` and `xsizemode` for more info.'
].join(' ')
},

x0shift: {
valType: 'number',
dflt: 0,
min: -1,
max: 1,
editType: 'calc',
description: [
'Shifts `x0` away from the center of the category when `xref` is a *category* or',
'*multicategory* axis. -0.5 corresponds to the start of the category and 0.5',
'corresponds to the end of the category.'
].join(' ')
},
x1shift: {
valType: 'number',
dflt: 0,
min: -1,
max: 1,
editType: 'calc',
description: [
'Shifts `x1` away from the center of the category when `xref` is a *category* or',
'*multicategory* axis. -0.5 corresponds to the start of the category and 0.5',
'corresponds to the end of the category.'
].join(' ')
},
yref: extendFlat({}, annAttrs.yref, {
description: [
'Sets the shape\'s y coordinate axis.',
Expand Down Expand Up @@ -220,7 +243,30 @@ module.exports = templatedArray('shape', {
'See `type` and `ysizemode` for more info.'
].join(' ')
},

y0shift: {
valType: 'number',
dflt: 0,
min: -1,
max: 1,
editType: 'calc',
description: [
'Shifts `y0` away from the center of the category when `yref` is a *category* or',
'*multicategory* axis. -0.5 corresponds to the start of the category and 0.5',
'corresponds to the end of the category.'
].join(' ')
},
y1shift: {
valType: 'number',
dflt: 0,
min: -1,
max: 1,
editType: 'calc',
description: [
'Shifts `y1` away from the center of the category when `yref` is a *category* or',
'*multicategory* axis. -0.5 corresponds to the start of the category and 0.5',
'corresponds to the end of the category.'
].join(' ')
},
path: {
valType: 'string',
editType: 'calc+arraydraw',
Expand Down
38 changes: 27 additions & 11 deletions src/components/shapes/calc_autorange.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,18 @@ module.exports = function calcAutorange(gd) {

// paper and axis domain referenced shapes don't affect autorange
if(shape.xref !== 'paper' && xRefType !== 'domain') {
var vx0 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x0;
var vx1 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x1;
ax = Axes.getFromId(gd, shape.xref);

bounds = shapeBounds(ax, vx0, vx1, shape.path, constants.paramIsX);
bounds = shapeBounds(ax, shape, constants.paramIsX);
if(bounds) {
shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcXPaddingOptions(shape));
}
}

if(shape.yref !== 'paper' && yRefType !== 'domain') {
var vy0 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y0;
var vy1 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y1;
ax = Axes.getFromId(gd, shape.yref);

bounds = shapeBounds(ax, vy0, vy1, shape.path, constants.paramIsY);
bounds = shapeBounds(ax, shape, constants.paramIsY);
if(bounds) {
shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcYPaddingOptions(shape));
}
Expand Down Expand Up @@ -77,15 +73,35 @@ function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) {
}
}

function shapeBounds(ax, v0, v1, path, paramsToUse) {
var convertVal = (ax.type === 'category' || ax.type === 'multicategory') ? ax.r2c : ax.d2c;
function shapeBounds(ax, shape, paramsToUse) {
var dim = ax._id.charAt(0) === 'x' ? 'x' : 'y';
var isCategory = ax.type === 'category' || ax.type === 'multicategory';
var v0;
var v1;
var shiftStart = 0;
var shiftEnd = 0;

var convertVal = isCategory ? ax.r2c : ax.d2c;

var isSizeModeScale = shape[dim + 'sizemode'] === 'scaled';
if(isSizeModeScale) {
v0 = shape[dim + '0'];
v1 = shape[dim + '1'];
if(isCategory) {
shiftStart = shape[dim + '0shift'];
shiftEnd = shape[dim + '1shift'];
}
} else {
v0 = shape[dim + 'anchor'];
v1 = shape[dim + 'anchor'];
}

if(v0 !== undefined) return [convertVal(v0), convertVal(v1)];
if(!path) return;
if(v0 !== undefined) return [convertVal(v0) + shiftStart, convertVal(v1) + shiftEnd];
if(!shape.path) return;

var min = Infinity;
var max = -Infinity;
var segments = path.match(constants.segmentRE);
var segments = shape.path.match(constants.segmentRE);
var i;
var segment;
var drawnParam;
Expand Down
4 changes: 4 additions & 0 deletions src/components/shapes/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
ax._shapeIndices.push(shapeOut._index);
r2pos = helpers.rangeToShapePosition(ax);
pos2r = helpers.shapePositionToRange(ax);
if(ax.type === 'category' || ax.type === 'multicategory') {
coerce(axLetter + '0shift');
coerce(axLetter + '1shift');
}
} else {
pos2r = r2pos = Lib.identity;
}
Expand Down
22 changes: 16 additions & 6 deletions src/components/shapes/display_labels.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,25 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) {
// and convert them to pixel coordinates
// Setup conversion functions
var xa = Axes.getFromId(gd, options.xref);
var xShiftStart = options.x0shift;
var xShiftEnd = options.x1shift;
var xRefType = Axes.getRefType(options.xref);
var ya = Axes.getFromId(gd, options.yref);
var yShiftStart = options.y0shift;
var yShiftEnd = options.y1shift;
var yRefType = Axes.getRefType(options.yref);
var x2p = helpers.getDataToPixel(gd, xa, false, xRefType);
var y2p = helpers.getDataToPixel(gd, ya, true, yRefType);
shapex0 = x2p(options.x0);
shapex1 = x2p(options.x1);
shapey0 = y2p(options.y0);
shapey1 = y2p(options.y1);
var x2p = function(v, shift) {
var dataToPixel = helpers.getDataToPixel(gd, xa, shift, false, xRefType);
return dataToPixel(v);
};
var y2p = function(v, shift) {
var dataToPixel = helpers.getDataToPixel(gd, ya, shift, true, yRefType);
return dataToPixel(v);
};
shapex0 = x2p(options.x0, xShiftStart);
shapex1 = x2p(options.x1, xShiftEnd);
shapey0 = y2p(options.y0, yShiftStart);
shapey1 = y2p(options.y1, yShiftEnd);
}

// Handle `auto` angle
Expand Down
22 changes: 16 additions & 6 deletions src/components/shapes/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,18 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
var xRefType = Axes.getRefType(shapeOptions.xref);
var ya = Axes.getFromId(gd, shapeOptions.yref);
var yRefType = Axes.getRefType(shapeOptions.yref);
var x2p = helpers.getDataToPixel(gd, xa, false, xRefType);
var y2p = helpers.getDataToPixel(gd, ya, true, yRefType);
var shiftXStart = shapeOptions.x0shift;
var shiftXEnd = shapeOptions.x1shift;
var shiftYStart = shapeOptions.y0shift;
var shiftYEnd = shapeOptions.y1shift;
var x2p = function(v, shift) {
var dataToPixel = helpers.getDataToPixel(gd, xa, shift, false, xRefType);
return dataToPixel(v);
};
var y2p = function(v, shift) {
var dataToPixel = helpers.getDataToPixel(gd, ya, shift, true, yRefType);
return dataToPixel(v);
};
var p2x = helpers.getPixelToData(gd, xa, false, xRefType);
var p2y = helpers.getPixelToData(gd, ya, true, yRefType);

Expand Down Expand Up @@ -279,8 +289,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
g.append('circle')
.attr({
'data-line-point': 'start-point',
cx: xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x0 : x2p(shapeOptions.x0),
cy: yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y0 : y2p(shapeOptions.y0),
cx: xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x0 : x2p(shapeOptions.x0, shiftXStart),
cy: yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y0 : y2p(shapeOptions.y0, shiftYStart),
r: circleRadius
})
.style(circleStyle)
Expand All @@ -289,8 +299,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
g.append('circle')
.attr({
'data-line-point': 'end-point',
cx: xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x1 : x2p(shapeOptions.x1),
cy: yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y1 : y2p(shapeOptions.y1),
cx: xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x1 : x2p(shapeOptions.x1, shiftXEnd),
cy: yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y1 : y2p(shapeOptions.y1, shiftYEnd),
r: circleRadius
})
.style(circleStyle)
Expand Down
8 changes: 4 additions & 4 deletions src/components/shapes/draw_newshape/newshapes.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ function newShapes(outlines, dragOptions) {
case 'line':
case 'rect':
case 'circle':
modifyItem('x0', afterEdit.x0);
modifyItem('x1', afterEdit.x1);
modifyItem('y0', afterEdit.y0);
modifyItem('y1', afterEdit.y1);
modifyItem('x0', afterEdit.x0 - (beforeEdit.x0shift || 0));
modifyItem('x1', afterEdit.x1 - (beforeEdit.x1shift || 0));
modifyItem('y0', afterEdit.y0 - (beforeEdit.y0shift || 0));
modifyItem('y1', afterEdit.y1 - (beforeEdit.y1shift || 0));
break;

case 'path':
Expand Down
35 changes: 24 additions & 11 deletions src/components/shapes/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ exports.extractPathCoords = function(path, paramsToUse, isRaw) {
return extractedCoordinates;
};

exports.getDataToPixel = function(gd, axis, isVertical, refType) {
exports.getDataToPixel = function(gd, axis, shift, isVertical, refType) {
var gs = gd._fullLayout._size;
var dataToPixel;

Expand All @@ -66,7 +66,8 @@ exports.getDataToPixel = function(gd, axis, isVertical, refType) {
var d2r = exports.shapePositionToRange(axis);

dataToPixel = function(v) {
return axis._offset + axis.r2p(d2r(v, true));
var shiftPixels = getPixelShift(axis, shift);
return axis._offset + axis.r2p(d2r(v, true)) + shiftPixels;
};

if(axis.type === 'date') dataToPixel = exports.decodeDate(dataToPixel);
Expand Down Expand Up @@ -179,6 +180,10 @@ exports.getPathString = function(gd, options) {
var ya = Axes.getFromId(gd, options.yref);
var gs = gd._fullLayout._size;
var x2r, x2p, y2r, y2p;
var xShiftStart = getPixelShift(xa, options.x0shift);
var xShiftEnd = getPixelShift(xa, options.x1shift);
var yShiftStart = getPixelShift(ya, options.y0shift);
var yShiftEnd = getPixelShift(ya, options.y1shift);
var x0, x1, y0, y1;

if(xa) {
Expand Down Expand Up @@ -208,23 +213,22 @@ exports.getPathString = function(gd, options) {
if(ya && ya.type === 'date') y2p = exports.decodeDate(y2p);
return convertPath(options, x2p, y2p);
}

if(options.xsizemode === 'pixel') {
var xAnchorPos = x2p(options.xanchor);
x0 = xAnchorPos + options.x0;
x1 = xAnchorPos + options.x1;
x0 = xAnchorPos + options.x0 + xShiftStart;
x1 = xAnchorPos + options.x1 + xShiftEnd;
} else {
x0 = x2p(options.x0);
x1 = x2p(options.x1);
x0 = x2p(options.x0) + xShiftStart;
x1 = x2p(options.x1) + xShiftEnd;
}

if(options.ysizemode === 'pixel') {
var yAnchorPos = y2p(options.yanchor);
y0 = yAnchorPos - options.y0;
y1 = yAnchorPos - options.y1;
y0 = yAnchorPos - options.y0 + yShiftStart;
y1 = yAnchorPos - options.y1 + yShiftEnd;
} else {
y0 = y2p(options.y0);
y1 = y2p(options.y1);
y0 = y2p(options.y0) + yShiftStart;
y1 = y2p(options.y1) + yShiftEnd;
}

if(type === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1;
Expand Down Expand Up @@ -279,3 +283,12 @@ function convertPath(options, x2p, y2p) {
return segmentType + paramString;
});
}

function getPixelShift(axis, shift) {
shift = shift || 0;
var shiftPixels = 0;
if(shift && axis && (axis.type === 'category' || axis.type === 'multicategory')) {
shiftPixels = (axis.r2p(1) - axis.r2p(0)) * shift;
}
return shiftPixels;
}
13 changes: 9 additions & 4 deletions src/components/shapes/label_texttemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ function x1Fn(shape) { return shape.x1; }
function y0Fn(shape) { return shape.y0; }
function y1Fn(shape) { return shape.y1; }

function x0shiftFn(shape) { return shape.x0shift || 0; }
function x1shiftFn(shape) { return shape.x1shift || 0; }
function y0shiftFn(shape) { return shape.y0shift || 0; }
function y1shiftFn(shape) { return shape.y1shift || 0; }

function dxFn(shape, xa) {
return d2l(shape.x1, xa) - d2l(shape.x0, xa);
return d2l(shape.x1, xa) + x1shiftFn(shape) - d2l(shape.x0, xa) - x0shiftFn(shape);
}

function dyFn(shape, xa, ya) {
return d2l(shape.y1, ya) - d2l(shape.y0, ya);
return d2l(shape.y1, ya) + y1shiftFn(shape) - d2l(shape.y0, ya) - y0shiftFn(shape);
}

function widthFn(shape, xa) {
Expand All @@ -41,11 +46,11 @@ function lengthFn(shape, xa, ya) {
}

function xcenterFn(shape, xa) {
return l2d((d2l(shape.x1, xa) + d2l(shape.x0, xa)) / 2, xa);
return l2d((d2l(shape.x1, xa) + x1shiftFn(shape) + d2l(shape.x0, xa) + x0shiftFn(shape)) / 2, xa);
}

function ycenterFn(shape, xa, ya) {
return l2d((d2l(shape.y1, ya) + d2l(shape.y0, ya)) / 2, ya);
return l2d((d2l(shape.y1, ya) + y1shiftFn(shape) + d2l(shape.y0, ya) + y0shiftFn(shape)) / 2, ya);
}

function slopeFn(shape, xa, ya) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/zzz_shape_shift_vertical.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e2653aa

Please sign in to comment.