Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e2653aa

Browse filesBrowse files
authored
Merge pull request #7005 from my-tien/shape_shift
property x0shift, x1shift, y0shift, y1shift for adjusting the shape coordinates
2 parents ec283ee + 9ac970a commit e2653aa
Copy full SHA for e2653aa

15 files changed

+391
-56
lines changed

‎draftlogs/7005_add.md

Copy file name to clipboard
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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)]

‎src/components/shapes/attributes.js

Copy file name to clipboardExpand all lines: src/components/shapes/attributes.js
+48-2Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,30 @@ module.exports = templatedArray('shape', {
169169
'See `type` and `xsizemode` for more info.'
170170
].join(' ')
171171
},
172-
172+
x0shift: {
173+
valType: 'number',
174+
dflt: 0,
175+
min: -1,
176+
max: 1,
177+
editType: 'calc',
178+
description: [
179+
'Shifts `x0` away from the center of the category when `xref` is a *category* or',
180+
'*multicategory* axis. -0.5 corresponds to the start of the category and 0.5',
181+
'corresponds to the end of the category.'
182+
].join(' ')
183+
},
184+
x1shift: {
185+
valType: 'number',
186+
dflt: 0,
187+
min: -1,
188+
max: 1,
189+
editType: 'calc',
190+
description: [
191+
'Shifts `x1` away from the center of the category when `xref` is a *category* or',
192+
'*multicategory* axis. -0.5 corresponds to the start of the category and 0.5',
193+
'corresponds to the end of the category.'
194+
].join(' ')
195+
},
173196
yref: extendFlat({}, annAttrs.yref, {
174197
description: [
175198
'Sets the shape\'s y coordinate axis.',
@@ -220,7 +243,30 @@ module.exports = templatedArray('shape', {
220243
'See `type` and `ysizemode` for more info.'
221244
].join(' ')
222245
},
223-
246+
y0shift: {
247+
valType: 'number',
248+
dflt: 0,
249+
min: -1,
250+
max: 1,
251+
editType: 'calc',
252+
description: [
253+
'Shifts `y0` away from the center of the category when `yref` is a *category* or',
254+
'*multicategory* axis. -0.5 corresponds to the start of the category and 0.5',
255+
'corresponds to the end of the category.'
256+
].join(' ')
257+
},
258+
y1shift: {
259+
valType: 'number',
260+
dflt: 0,
261+
min: -1,
262+
max: 1,
263+
editType: 'calc',
264+
description: [
265+
'Shifts `y1` away from the center of the category when `yref` is a *category* or',
266+
'*multicategory* axis. -0.5 corresponds to the start of the category and 0.5',
267+
'corresponds to the end of the category.'
268+
].join(' ')
269+
},
224270
path: {
225271
valType: 'string',
226272
editType: 'calc+arraydraw',

‎src/components/shapes/calc_autorange.js

Copy file name to clipboardExpand all lines: src/components/shapes/calc_autorange.js
+27-11Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,18 @@ module.exports = function calcAutorange(gd) {
2323

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

30-
bounds = shapeBounds(ax, vx0, vx1, shape.path, constants.paramIsX);
28+
bounds = shapeBounds(ax, shape, constants.paramIsX);
3129
if(bounds) {
3230
shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcXPaddingOptions(shape));
3331
}
3432
}
3533

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

41-
bounds = shapeBounds(ax, vy0, vy1, shape.path, constants.paramIsY);
37+
bounds = shapeBounds(ax, shape, constants.paramIsY);
4238
if(bounds) {
4339
shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcYPaddingOptions(shape));
4440
}
@@ -77,15 +73,35 @@ function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) {
7773
}
7874
}
7975

80-
function shapeBounds(ax, v0, v1, path, paramsToUse) {
81-
var convertVal = (ax.type === 'category' || ax.type === 'multicategory') ? ax.r2c : ax.d2c;
76+
function shapeBounds(ax, shape, paramsToUse) {
77+
var dim = ax._id.charAt(0) === 'x' ? 'x' : 'y';
78+
var isCategory = ax.type === 'category' || ax.type === 'multicategory';
79+
var v0;
80+
var v1;
81+
var shiftStart = 0;
82+
var shiftEnd = 0;
83+
84+
var convertVal = isCategory ? ax.r2c : ax.d2c;
85+
86+
var isSizeModeScale = shape[dim + 'sizemode'] === 'scaled';
87+
if(isSizeModeScale) {
88+
v0 = shape[dim + '0'];
89+
v1 = shape[dim + '1'];
90+
if(isCategory) {
91+
shiftStart = shape[dim + '0shift'];
92+
shiftEnd = shape[dim + '1shift'];
93+
}
94+
} else {
95+
v0 = shape[dim + 'anchor'];
96+
v1 = shape[dim + 'anchor'];
97+
}
8298

83-
if(v0 !== undefined) return [convertVal(v0), convertVal(v1)];
84-
if(!path) return;
99+
if(v0 !== undefined) return [convertVal(v0) + shiftStart, convertVal(v1) + shiftEnd];
100+
if(!shape.path) return;
85101

86102
var min = Infinity;
87103
var max = -Infinity;
88-
var segments = path.match(constants.segmentRE);
104+
var segments = shape.path.match(constants.segmentRE);
89105
var i;
90106
var segment;
91107
var drawnParam;

‎src/components/shapes/defaults.js

Copy file name to clipboardExpand all lines: src/components/shapes/defaults.js
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
8585
ax._shapeIndices.push(shapeOut._index);
8686
r2pos = helpers.rangeToShapePosition(ax);
8787
pos2r = helpers.shapePositionToRange(ax);
88+
if(ax.type === 'category' || ax.type === 'multicategory') {
89+
coerce(axLetter + '0shift');
90+
coerce(axLetter + '1shift');
91+
}
8892
} else {
8993
pos2r = r2pos = Lib.identity;
9094
}

‎src/components/shapes/display_labels.js

Copy file name to clipboardExpand all lines: src/components/shapes/display_labels.js
+16-6Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,25 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) {
8888
// and convert them to pixel coordinates
8989
// Setup conversion functions
9090
var xa = Axes.getFromId(gd, options.xref);
91+
var xShiftStart = options.x0shift;
92+
var xShiftEnd = options.x1shift;
9193
var xRefType = Axes.getRefType(options.xref);
9294
var ya = Axes.getFromId(gd, options.yref);
95+
var yShiftStart = options.y0shift;
96+
var yShiftEnd = options.y1shift;
9397
var yRefType = Axes.getRefType(options.yref);
94-
var x2p = helpers.getDataToPixel(gd, xa, false, xRefType);
95-
var y2p = helpers.getDataToPixel(gd, ya, true, yRefType);
96-
shapex0 = x2p(options.x0);
97-
shapex1 = x2p(options.x1);
98-
shapey0 = y2p(options.y0);
99-
shapey1 = y2p(options.y1);
98+
var x2p = function(v, shift) {
99+
var dataToPixel = helpers.getDataToPixel(gd, xa, shift, false, xRefType);
100+
return dataToPixel(v);
101+
};
102+
var y2p = function(v, shift) {
103+
var dataToPixel = helpers.getDataToPixel(gd, ya, shift, true, yRefType);
104+
return dataToPixel(v);
105+
};
106+
shapex0 = x2p(options.x0, xShiftStart);
107+
shapex1 = x2p(options.x1, xShiftEnd);
108+
shapey0 = y2p(options.y0, yShiftStart);
109+
shapey1 = y2p(options.y1, yShiftEnd);
100110
}
101111

102112
// Handle `auto` angle

‎src/components/shapes/draw.js

Copy file name to clipboardExpand all lines: src/components/shapes/draw.js
+16-6Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,18 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
227227
var xRefType = Axes.getRefType(shapeOptions.xref);
228228
var ya = Axes.getFromId(gd, shapeOptions.yref);
229229
var yRefType = Axes.getRefType(shapeOptions.yref);
230-
var x2p = helpers.getDataToPixel(gd, xa, false, xRefType);
231-
var y2p = helpers.getDataToPixel(gd, ya, true, yRefType);
230+
var shiftXStart = shapeOptions.x0shift;
231+
var shiftXEnd = shapeOptions.x1shift;
232+
var shiftYStart = shapeOptions.y0shift;
233+
var shiftYEnd = shapeOptions.y1shift;
234+
var x2p = function(v, shift) {
235+
var dataToPixel = helpers.getDataToPixel(gd, xa, shift, false, xRefType);
236+
return dataToPixel(v);
237+
};
238+
var y2p = function(v, shift) {
239+
var dataToPixel = helpers.getDataToPixel(gd, ya, shift, true, yRefType);
240+
return dataToPixel(v);
241+
};
232242
var p2x = helpers.getPixelToData(gd, xa, false, xRefType);
233243
var p2y = helpers.getPixelToData(gd, ya, true, yRefType);
234244

@@ -279,8 +289,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
279289
g.append('circle')
280290
.attr({
281291
'data-line-point': 'start-point',
282-
cx: xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x0 : x2p(shapeOptions.x0),
283-
cy: yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y0 : y2p(shapeOptions.y0),
292+
cx: xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x0 : x2p(shapeOptions.x0, shiftXStart),
293+
cy: yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y0 : y2p(shapeOptions.y0, shiftYStart),
284294
r: circleRadius
285295
})
286296
.style(circleStyle)
@@ -289,8 +299,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
289299
g.append('circle')
290300
.attr({
291301
'data-line-point': 'end-point',
292-
cx: xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x1 : x2p(shapeOptions.x1),
293-
cy: yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y1 : y2p(shapeOptions.y1),
302+
cx: xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x1 : x2p(shapeOptions.x1, shiftXEnd),
303+
cy: yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y1 : y2p(shapeOptions.y1, shiftYEnd),
294304
r: circleRadius
295305
})
296306
.style(circleStyle)

‎src/components/shapes/draw_newshape/newshapes.js

Copy file name to clipboardExpand all lines: src/components/shapes/draw_newshape/newshapes.js
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ function newShapes(outlines, dragOptions) {
8484
case 'line':
8585
case 'rect':
8686
case 'circle':
87-
modifyItem('x0', afterEdit.x0);
88-
modifyItem('x1', afterEdit.x1);
89-
modifyItem('y0', afterEdit.y0);
90-
modifyItem('y1', afterEdit.y1);
87+
modifyItem('x0', afterEdit.x0 - (beforeEdit.x0shift || 0));
88+
modifyItem('x1', afterEdit.x1 - (beforeEdit.x1shift || 0));
89+
modifyItem('y0', afterEdit.y0 - (beforeEdit.y0shift || 0));
90+
modifyItem('y1', afterEdit.y1 - (beforeEdit.y1shift || 0));
9191
break;
9292

9393
case 'path':

‎src/components/shapes/helpers.js

Copy file name to clipboardExpand all lines: src/components/shapes/helpers.js
+24-11Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ exports.extractPathCoords = function(path, paramsToUse, isRaw) {
5353
return extractedCoordinates;
5454
};
5555

56-
exports.getDataToPixel = function(gd, axis, isVertical, refType) {
56+
exports.getDataToPixel = function(gd, axis, shift, isVertical, refType) {
5757
var gs = gd._fullLayout._size;
5858
var dataToPixel;
5959

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

6868
dataToPixel = function(v) {
69-
return axis._offset + axis.r2p(d2r(v, true));
69+
var shiftPixels = getPixelShift(axis, shift);
70+
return axis._offset + axis.r2p(d2r(v, true)) + shiftPixels;
7071
};
7172

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

184189
if(xa) {
@@ -208,23 +213,22 @@ exports.getPathString = function(gd, options) {
208213
if(ya && ya.type === 'date') y2p = exports.decodeDate(y2p);
209214
return convertPath(options, x2p, y2p);
210215
}
211-
212216
if(options.xsizemode === 'pixel') {
213217
var xAnchorPos = x2p(options.xanchor);
214-
x0 = xAnchorPos + options.x0;
215-
x1 = xAnchorPos + options.x1;
218+
x0 = xAnchorPos + options.x0 + xShiftStart;
219+
x1 = xAnchorPos + options.x1 + xShiftEnd;
216220
} else {
217-
x0 = x2p(options.x0);
218-
x1 = x2p(options.x1);
221+
x0 = x2p(options.x0) + xShiftStart;
222+
x1 = x2p(options.x1) + xShiftEnd;
219223
}
220224

221225
if(options.ysizemode === 'pixel') {
222226
var yAnchorPos = y2p(options.yanchor);
223-
y0 = yAnchorPos - options.y0;
224-
y1 = yAnchorPos - options.y1;
227+
y0 = yAnchorPos - options.y0 + yShiftStart;
228+
y1 = yAnchorPos - options.y1 + yShiftEnd;
225229
} else {
226-
y0 = y2p(options.y0);
227-
y1 = y2p(options.y1);
230+
y0 = y2p(options.y0) + yShiftStart;
231+
y1 = y2p(options.y1) + yShiftEnd;
228232
}
229233

230234
if(type === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1;
@@ -279,3 +283,12 @@ function convertPath(options, x2p, y2p) {
279283
return segmentType + paramString;
280284
});
281285
}
286+
287+
function getPixelShift(axis, shift) {
288+
shift = shift || 0;
289+
var shiftPixels = 0;
290+
if(shift && axis && (axis.type === 'category' || axis.type === 'multicategory')) {
291+
shiftPixels = (axis.r2p(1) - axis.r2p(0)) * shift;
292+
}
293+
return shiftPixels;
294+
}

‎src/components/shapes/label_texttemplate.js

Copy file name to clipboardExpand all lines: src/components/shapes/label_texttemplate.js
+9-4Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ function x1Fn(shape) { return shape.x1; }
1616
function y0Fn(shape) { return shape.y0; }
1717
function y1Fn(shape) { return shape.y1; }
1818

19+
function x0shiftFn(shape) { return shape.x0shift || 0; }
20+
function x1shiftFn(shape) { return shape.x1shift || 0; }
21+
function y0shiftFn(shape) { return shape.y0shift || 0; }
22+
function y1shiftFn(shape) { return shape.y1shift || 0; }
23+
1924
function dxFn(shape, xa) {
20-
return d2l(shape.x1, xa) - d2l(shape.x0, xa);
25+
return d2l(shape.x1, xa) + x1shiftFn(shape) - d2l(shape.x0, xa) - x0shiftFn(shape);
2126
}
2227

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

2732
function widthFn(shape, xa) {
@@ -41,11 +46,11 @@ function lengthFn(shape, xa, ya) {
4146
}
4247

4348
function xcenterFn(shape, xa) {
44-
return l2d((d2l(shape.x1, xa) + d2l(shape.x0, xa)) / 2, xa);
49+
return l2d((d2l(shape.x1, xa) + x1shiftFn(shape) + d2l(shape.x0, xa) + x0shiftFn(shape)) / 2, xa);
4550
}
4651

4752
function ycenterFn(shape, xa, ya) {
48-
return l2d((d2l(shape.y1, ya) + d2l(shape.y0, ya)) / 2, ya);
53+
return l2d((d2l(shape.y1, ya) + y1shiftFn(shape) + d2l(shape.y0, ya) + y0shiftFn(shape)) / 2, ya);
4954
}
5055

5156
function slopeFn(shape, xa, ya) {
Loading
Loading

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.