Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

property x0shift, x1shift, y0shift, y1shift for adjusting the shape coordinates #7005

Merged
merged 26 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
aa0cd7a
property shape.x_shift/y_shift for adjusting the shape coordinates
my-tien May 27, 2024
44d1858
Add draftlog for PR 7005
my-tien May 27, 2024
7abea61
Add x_shift/y_shift for selections as well, add selection example to …
my-tien May 27, 2024
a617017
baseline images zzz_shape_shift_horizontal and zzz_shape_shift_vertical
my-tien May 27, 2024
c2f7f27
Update shapes_test after adding shift param to getDataToPixel
my-tien May 27, 2024
fdcaa0c
Rename x_shift/y_shift → xshift/yshift
my-tien May 31, 2024
9921522
Replace xshift and yshift with x0shift and y0shift
my-tien Jun 10, 2024
3ac32f3
Only coerce x0/x1/y0/y1shift if referenced axis is category/multicate…
my-tien Jun 10, 2024
6680745
Fix typo in calc_autorange: x/ysizemode "scale" -> "scaled"
my-tien Jun 10, 2024
ada2289
Add scatter to zzz_shape_shift_vertical mock
my-tien Jun 10, 2024
e11f6f6
Merge remote-tracking branch 'origin-plotly/master' into shape_shift
my-tien Jun 10, 2024
1bfb22d
Updated baseline image after adding scatter to zzz_shape_shift_vertic…
my-tien Jun 10, 2024
04466db
getDataToPixel: If shift is undefined, set it to 0
my-tien Jun 10, 2024
91db7c8
Update draftlog with final property names
my-tien Jun 12, 2024
4bbb669
Adjust attribute descriptions with property formatting and mention of…
my-tien Jun 12, 2024
ab935ac
Move calculation of shape shift into a dedicated function
my-tien Jun 18, 2024
82de6b1
Merge remote-tracking branch 'origin-plotly/master' into shape_shift
my-tien Jun 19, 2024
f9c44bf
Update overlooked test after changing xshift/yshift to x0shift,x1shif…
my-tien Jul 3, 2024
b00d4d8
Remove shift properties from selection for now
my-tien Jul 10, 2024
8cf665a
Merge remote-tracking branch 'origin-plotly/master' into shape_shift
my-tien Jul 10, 2024
78a7e5e
Fix getPixelShift for reversed axes, add reversed axis to mock
my-tien Jul 11, 2024
18e7c84
Update baseline image of zzz_shape_shift_vertical
my-tien Jul 11, 2024
5509ee6
Account for shift when dragging a shape
my-tien Jul 15, 2024
6f8e5a6
Support shape shift in texttemplate as well
my-tien Jul 15, 2024
7de45c3
Update baseline image after updated mock for texttemplate shape
my-tien Jul 15, 2024
9ac970a
Refine description for x0shift/x1shift/y0shift/y1shift
my-tien Jul 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)]
46 changes: 44 additions & 2 deletions src/components/shapes/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,28 @@ module.exports = templatedArray('shape', {
'See `type` and `xsizemode` for more info.'
].join(' ')
},

x0shift: {
valType: 'number',
dflt: 0,
min: -1,
max: 1,
editType: 'calc',
description: [
'Only relevant if `xref` is a (multi-)category axes. Shifts `x0` by a fraction of the',
archmoj marked this conversation as resolved.
Show resolved Hide resolved
'reference unit. E.g. -0.5 corresponds to the start of the category and 0.5 corresponds to the end of the category.'
archmoj marked this conversation as resolved.
Show resolved Hide resolved
].join(' ')
archmoj marked this conversation as resolved.
Show resolved Hide resolved
},
x1shift: {
valType: 'number',
dflt: 0,
min: -1,
max: 1,
editType: 'calc',
description: [
'Only relevant if `xref` is a (multi-)category axes. Shifts `x1` by a fraction of the',
'reference unit. E.g. -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 +241,28 @@ module.exports = templatedArray('shape', {
'See `type` and `ysizemode` for more info.'
].join(' ')
},

y0shift: {
valType: 'number',
dflt: 0,
min: -1,
max: 1,
editType: 'calc',
description: [
'Only relevant if `yref` is a (multi-)category axes. Shifts `y0` by a fraction of the',
'reference unit. E.g. -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: [
'Only relevant if `yref` is a (multi-)category axes. Shifts `y1` by a fraction of the',
'reference unit. E.g. -0.5 corresponds to the start of the category and 0.5 corresponds to the end of the category.'
].join(' ')
},
archmoj marked this conversation as resolved.
Show resolved Hide resolved
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;
archmoj marked this conversation as resolved.
Show resolved Hide resolved

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
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) {
archmoj marked this conversation as resolved.
Show resolved Hide resolved
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;
archmoj marked this conversation as resolved.
Show resolved Hide resolved
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;
}
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.
89 changes: 89 additions & 0 deletions test/image/mocks/zzz_shape_shift_horizontal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"data": [
{
archmoj marked this conversation as resolved.
Show resolved Hide resolved
"y": [
["A", "A", "B", "B"], ["C", "D", "C", "D"]
],
"x": [
1, 2, 3, 4
],
"type": "bar",
"marker": {
"color": "rgba(153, 217, 234, 1)"
},
"orientation": "h"
},
{
"y": [
["A", "A", "B", "B"], ["C", "D", "C", "D"]
],
"x": [
4, 3, 2, 1
],
"type": "bar",
"orientation": "h"
}
],
"layout": {
"shapes": [
{
"layer": "above",
"type": "rect",
"label": {
"text": "around A,D",
"textposition": "bottom center",
"textangle": 0
},
"line": {
"color": "black",
"width": 3.0
},
"y0": ["A", "D"],
"y1": ["A", "D"],
"x0": 0,
"x1": 0.25,
"y0shift": -0.5,
"y1shift": 0.5,
"xref": "paper"
},
{
"layer": "above",
"type": "line",
"label": {
"text": "on B,D",
"textposition": "middle",
"textangle": 0
},
"line": {
"color": "blue",
"width": 3.0
},
"y0": ["B", "D"],
"y1": ["B", "D"],
"x0": 0,
"x1": 0.25,
"xref": "paper"
},
{
"layer": "above",
"type": "line",
"label": {
"text": "Before B,D",
"textposition": "middle",
"textangle": 0
},
"line": {
"color": "green",
"width": 3.0
},
"y0": ["B", "D"],
"y1": ["B", "D"],
"x0": 0,
"x1": 0.25,
"y0shift": -0.5,
"y1shift": -0.5,
"xref": "paper"
}
]
}
}
Loading