Skip to content

Commit d3208e9

Browse files
committed
Implement coordinate value coercion for array refs
1 parent 4d70fd3 commit d3208e9

5 files changed

Lines changed: 203 additions & 103 deletions

File tree

src/components/shapes/calc_autorange.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ module.exports = function calcAutorange(gd) {
2222
var yRefType = Axes.getRefType(shape.yref);
2323

2424
// paper and axis domain referenced shapes don't affect autorange
25-
if(shape.xref !== 'paper' && xRefType !== 'domain') {
25+
// TODO: implement autorange calculation for array ref shapes
26+
if(xRefType !== 'array' && shape.xref !== 'paper' && xRefType !== 'domain') {
2627
ax = Axes.getFromId(gd, shape.xref);
2728

2829
bounds = shapeBounds(ax, shape, constants.paramIsX);
@@ -31,7 +32,8 @@ module.exports = function calcAutorange(gd) {
3132
}
3233
}
3334

34-
if(shape.yref !== 'paper' && yRefType !== 'domain') {
35+
// TODO: implement autorange calculation for array ref shapes
36+
if(yRefType !== 'array' && shape.yref !== 'paper' && yRefType !== 'domain') {
3537
ax = Axes.getFromId(gd, shape.yref);
3638

3739
bounds = shapeBounds(ax, shape, constants.paramIsY);

src/components/shapes/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = {
3333
Q: {1: true, 3: true, drawn: 3},
3434
C: {1: true, 3: true, 5: true, drawn: 5},
3535
T: {1: true, drawn: 1},
36-
S: {1: true, 3: true, drawn: 5},
36+
S: {1: true, 3: true, drawn: 4},
3737
// A: {1: true, 6: true},
3838
Z: {}
3939
},

src/components/shapes/defaults.js

Lines changed: 93 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -98,63 +98,107 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
9898
axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, 'paper');
9999
}
100100

101-
var axRefType = Axes.getRefType(axRef);
102-
103-
if(axRefType === 'range') {
104-
ax = Axes.getFromId(gdMock, axRef);
105-
ax._shapeIndices.push(shapeOut._index);
106-
r2pos = helpers.rangeToShapePosition(ax);
107-
pos2r = helpers.shapePositionToRange(ax);
108-
if(ax.type === 'category' || ax.type === 'multicategory') {
109-
coerce(axLetter + '0shift');
110-
coerce(axLetter + '1shift');
111-
}
112-
} else {
113-
pos2r = r2pos = Lib.identity;
114-
}
101+
if(Array.isArray(axRef)) {
102+
var dflts = [0.25, 0.75];
103+
var pixelDflts = [0, 10];
104+
105+
// For each coordinate, coerce the position with their respective axis ref
106+
[0, 1].forEach(function(i) {
107+
var ref = axRef[i];
108+
var refType = Axes.getRefType(ref);
109+
if(refType === 'range') {
110+
ax = Axes.getFromId(gdMock, ref);
111+
pos2r = helpers.shapePositionToRange(ax);
112+
r2pos = helpers.rangeToShapePosition(ax);
113+
if(ax.type === 'category' || ax.type === 'multicategory') {
114+
coerce(axLetter + i + 'shift');
115+
}
116+
} else {
117+
pos2r = r2pos = Lib.identity;
118+
}
115119

116-
// Coerce x0, x1, y0, y1
117-
if(noPath) {
118-
var dflt0 = 0.25;
119-
var dflt1 = 0.75;
120-
121-
// hack until V3.0 when log has regular range behavior - make it look like other
122-
// ranges to send to coerce, then put it back after
123-
// this is all to give reasonable default position behavior on log axes, which is
124-
// a pretty unimportant edge case so we could just ignore this.
125-
var attr0 = axLetter + '0';
126-
var attr1 = axLetter + '1';
127-
var in0 = shapeIn[attr0];
128-
var in1 = shapeIn[attr1];
129-
shapeIn[attr0] = pos2r(shapeIn[attr0], true);
130-
shapeIn[attr1] = pos2r(shapeIn[attr1], true);
120+
if(noPath) {
121+
var attr = axLetter + i;
122+
var inValue = shapeIn[attr];
123+
shapeIn[attr] = pos2r(shapeIn[attr], true);
131124

132-
if(sizeMode === 'pixel') {
133-
coerce(attr0, 0);
134-
coerce(attr1, 10);
125+
if(sizeMode === 'pixel') {
126+
coerce(attr, pixelDflts[i]);
127+
} else {
128+
Axes.coercePosition(shapeOut, gdMock, coerce, ref, attr, dflts[i]);
129+
}
130+
131+
shapeOut[attr] = r2pos(shapeOut[attr]);
132+
shapeIn[attr] = inValue;
133+
}
134+
135+
if(i === 0 && sizeMode === 'pixel') {
136+
var inAnchor = shapeIn[attrAnchor];
137+
shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true);
138+
Axes.coercePosition(shapeOut, gdMock, coerce, ref, attrAnchor, 0.25);
139+
shapeOut[attrAnchor] = r2pos(shapeOut[attrAnchor]);
140+
shapeIn[attrAnchor] = inAnchor;
141+
}
142+
});
143+
} else {
144+
var axRefType = Axes.getRefType(axRef);
145+
146+
if(axRefType === 'range') {
147+
ax = Axes.getFromId(gdMock, axRef);
148+
ax._shapeIndices.push(shapeOut._index);
149+
r2pos = helpers.rangeToShapePosition(ax);
150+
pos2r = helpers.shapePositionToRange(ax);
151+
if(ax.type === 'category' || ax.type === 'multicategory') {
152+
coerce(axLetter + '0shift');
153+
coerce(axLetter + '1shift');
154+
}
135155
} else {
136-
Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0);
137-
Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1);
156+
pos2r = r2pos = Lib.identity;
138157
}
139158

140-
// hack part 2
141-
shapeOut[attr0] = r2pos(shapeOut[attr0]);
142-
shapeOut[attr1] = r2pos(shapeOut[attr1]);
143-
shapeIn[attr0] = in0;
144-
shapeIn[attr1] = in1;
145-
}
159+
// Coerce x0, x1, y0, y1
160+
if(noPath) {
161+
var dflt0 = 0.25;
162+
var dflt1 = 0.75;
163+
164+
// hack until V3.0 when log has regular range behavior - make it look like other
165+
// ranges to send to coerce, then put it back after
166+
// this is all to give reasonable default position behavior on log axes, which is
167+
// a pretty unimportant edge case so we could just ignore this.
168+
var attr0 = axLetter + '0';
169+
var attr1 = axLetter + '1';
170+
var in0 = shapeIn[attr0];
171+
var in1 = shapeIn[attr1];
172+
shapeIn[attr0] = pos2r(shapeIn[attr0], true);
173+
shapeIn[attr1] = pos2r(shapeIn[attr1], true);
174+
175+
if(sizeMode === 'pixel') {
176+
coerce(attr0, 0);
177+
coerce(attr1, 10);
178+
} else {
179+
Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0);
180+
Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1);
181+
}
146182

147-
// Coerce xanchor and yanchor
148-
if(sizeMode === 'pixel') {
149-
// Hack for log axis described above
150-
var inAnchor = shapeIn[attrAnchor];
151-
shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true);
183+
// hack part 2
184+
shapeOut[attr0] = r2pos(shapeOut[attr0]);
185+
shapeOut[attr1] = r2pos(shapeOut[attr1]);
186+
shapeIn[attr0] = in0;
187+
shapeIn[attr1] = in1;
188+
}
152189

153-
Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attrAnchor, 0.25);
190+
// Coerce xanchor and yanchor
191+
if(sizeMode === 'pixel') {
192+
// Hack for log axis described above
193+
var inAnchor = shapeIn[attrAnchor];
194+
shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true);
154195

155-
// Hack part 2
156-
shapeOut[attrAnchor] = r2pos(shapeOut[attrAnchor]);
157-
shapeIn[attrAnchor] = inAnchor;
196+
Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attrAnchor, 0.25);
197+
198+
// Hack part 2
199+
shapeOut[attrAnchor] = r2pos(shapeOut[attrAnchor]);
200+
shapeIn[attrAnchor] = inAnchor;
201+
}
158202
}
159203
});
160204

src/components/shapes/helpers.js

Lines changed: 98 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -190,66 +190,104 @@ exports.makeSelectionsOptionsAndPlotinfo = function(gd, index) {
190190

191191

192192
exports.getPathString = function(gd, options) {
193-
var type = options.type;
193+
var shapeType = options.type;
194194
var xRefType = Axes.getRefType(options.xref);
195195
var yRefType = Axes.getRefType(options.yref);
196-
var xa = Axes.getFromId(gd, options.xref);
197-
var ya = Axes.getFromId(gd, options.yref);
198196
var gs = gd._fullLayout._size;
199-
var x2r, x2p, y2r, y2p;
200-
var xShiftStart = getPixelShift(xa, options.x0shift);
201-
var xShiftEnd = getPixelShift(xa, options.x1shift);
202-
var yShiftStart = getPixelShift(ya, options.y0shift);
203-
var yShiftEnd = getPixelShift(ya, options.y1shift);
197+
var xa, ya;
198+
var xShiftStart, xShiftEnd, yShiftStart, yShiftEnd;
199+
var x2p, y2p;
204200
var x0, x1, y0, y1;
205201

206-
if(xa) {
207-
if(xRefType === 'domain') {
208-
x2p = function(v) { return xa._offset + xa._length * v; };
202+
function getConverter(axis, refType, shapeType, isVertical) {
203+
var converter;
204+
if(axis) {
205+
if(refType === 'domain') {
206+
if(isVertical) {
207+
converter = function(v) { return axis._offset + axis._length * (1 - v); };
208+
} else {
209+
converter = function(v) { return axis._offset + axis._length * v; };
210+
}
211+
} else {
212+
var d2r = exports.shapePositionToRange(axis);
213+
converter = function(v) { return axis._offset + axis.r2p(d2r(v, true)); };
214+
215+
if(shapeType === 'path' && axis.type === 'date') converter = exports.decodeDate(converter);
216+
}
209217
} else {
210-
x2r = exports.shapePositionToRange(xa);
211-
x2p = function(v) { return xa._offset + xa.r2p(x2r(v, true)); };
218+
if(isVertical) {
219+
converter = function(v) { return gs.t + gs.h * (1 - v); };
220+
} else {
221+
converter = function(v) { return gs.l + gs.w * v; };
222+
}
212223
}
213-
} else {
214-
x2p = function(v) { return gs.l + gs.w * v; };
215-
}
216224

217-
if(ya) {
218-
if(yRefType === 'domain') {
219-
y2p = function(v) { return ya._offset + ya._length * (1 - v); };
220-
} else {
221-
y2r = exports.shapePositionToRange(ya);
222-
y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); };
223-
}
224-
} else {
225-
y2p = function(v) { return gs.t + gs.h * (1 - v); };
225+
return converter;
226226
}
227227

228-
if(type === 'path') {
229-
if(xa && xa.type === 'date') x2p = exports.decodeDate(x2p);
230-
if(ya && ya.type === 'date') y2p = exports.decodeDate(y2p);
231-
return convertPath(options, x2p, y2p);
228+
// Build function(s) to convert data to pixel
229+
if(xRefType === 'array') {
230+
x2p = [];
231+
xa = options.xref.map(function(ref) { return Axes.getFromId(gd, ref); });
232+
x2p = options.xref.map(function(ref, i) {
233+
return getConverter(xa[i], Axes.getRefType(ref), shapeType, false);
234+
});
235+
} else {
236+
xa = Axes.getFromId(gd, options.xref);
237+
x2p = getConverter(xa, xRefType, shapeType, false);
232238
}
233-
if(options.xsizemode === 'pixel') {
234-
var xAnchorPos = x2p(options.xanchor);
235-
x0 = xAnchorPos + options.x0 + xShiftStart;
236-
x1 = xAnchorPos + options.x1 + xShiftEnd;
239+
if(yRefType === 'array') {
240+
y2p = [];
241+
ya = options.yref.map(function(ref) { return Axes.getFromId(gd, ref); });
242+
y2p = options.yref.map(function(ref, i) {
243+
return getConverter(ya[i], Axes.getRefType(ref), shapeType, true);
244+
});
237245
} else {
238-
x0 = x2p(options.x0) + xShiftStart;
239-
x1 = x2p(options.x1) + xShiftEnd;
246+
ya = Axes.getFromId(gd, options.yref);
247+
y2p = getConverter(ya, yRefType, shapeType, true);
240248
}
241249

242-
if(options.ysizemode === 'pixel') {
243-
var yAnchorPos = y2p(options.yanchor);
244-
y0 = yAnchorPos - options.y0 + yShiftStart;
245-
y1 = yAnchorPos - options.y1 + yShiftEnd;
250+
if(shapeType === 'path') { return convertPath(options, x2p, y2p); }
251+
252+
// Calculate pixel coordinates for non-path shapes
253+
// Pixel sizemode for array refs is not supported for now
254+
if(xRefType === 'array') {
255+
xShiftStart = getPixelShift(xa[0], options.x0shift);
256+
xShiftEnd = getPixelShift(xa[1], options.x1shift);
257+
x0 = x2p[0](options.x0) + xShiftStart;
258+
x1 = x2p[1](options.x1) + xShiftEnd;
259+
} else {
260+
xShiftStart = getPixelShift(xa, options.x0shift);
261+
xShiftEnd = getPixelShift(xa, options.x1shift);
262+
if(options.xsizemode === 'pixel') {
263+
var xAnchorPos = x2p(options.xanchor);
264+
x0 = xAnchorPos + options.x0 + xShiftStart;
265+
x1 = xAnchorPos + options.x1 + xShiftEnd;
266+
} else {
267+
x0 = x2p(options.x0) + xShiftStart;
268+
x1 = x2p(options.x1) + xShiftEnd;
269+
}
270+
}
271+
if(yRefType === 'array') {
272+
yShiftStart = getPixelShift(ya[0], options.y0shift);
273+
yShiftEnd = getPixelShift(ya[1], options.y1shift);
274+
y0 = y2p[0](options.y0) + yShiftStart;
275+
y1 = y2p[1](options.y1) + yShiftEnd;
246276
} else {
247-
y0 = y2p(options.y0) + yShiftStart;
248-
y1 = y2p(options.y1) + yShiftEnd;
277+
yShiftStart = getPixelShift(ya, options.y0shift);
278+
yShiftEnd = getPixelShift(ya, options.y1shift);
279+
if(options.ysizemode === 'pixel') {
280+
var yAnchorPos = y2p(options.yanchor);
281+
y0 = yAnchorPos - options.y0 + yShiftStart;
282+
y1 = yAnchorPos - options.y1 + yShiftEnd;
283+
} else {
284+
y0 = y2p(options.y0) + yShiftStart;
285+
y1 = y2p(options.y1) + yShiftEnd;
286+
}
249287
}
250288

251-
if(type === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1;
252-
if(type === 'rect') return 'M' + x0 + ',' + y0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z';
289+
if(shapeType === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1;
290+
if(shapeType === 'rect') return 'M' + x0 + ',' + y0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z';
253291

254292
// circle
255293
var cx = (x0 + x1) / 2;
@@ -263,28 +301,37 @@ exports.getPathString = function(gd, options) {
263301
rArc + ' 0 0,1 ' + rightPt + 'Z';
264302
};
265303

266-
267304
function convertPath(options, x2p, y2p) {
268305
var pathIn = options.path;
269306
var xSizemode = options.xsizemode;
270307
var ySizemode = options.ysizemode;
271308
var xAnchor = options.xanchor;
272309
var yAnchor = options.yanchor;
310+
var isArrayXref = Array.isArray(options.xref);
311+
var isArrayYref = Array.isArray(options.yref);
312+
var xVertexIndex = 0;
313+
var yVertexIndex = 0;
273314

274315
return pathIn.replace(constants.segmentRE, function(segment) {
275316
var paramNumber = 0;
276317
var segmentType = segment.charAt(0);
277318
var xParams = constants.paramIsX[segmentType];
278319
var yParams = constants.paramIsY[segmentType];
279320
var nParams = constants.numParams[segmentType];
321+
var hasDrawnX = xParams.drawn !== undefined;
322+
var hasDrawnY = yParams.drawn !== undefined;
323+
324+
// Use vertex indices for array refs (same converter for all params in segment)
325+
var segmentX2p = (isArrayXref && xSizemode !== 'pixel') ? x2p[xVertexIndex] : x2p;
326+
var segmentY2p = (isArrayYref && ySizemode !== 'pixel') ? y2p[yVertexIndex] : y2p;
280327

281328
var paramString = segment.substr(1).replace(constants.paramRE, function(param) {
282329
if(xParams[paramNumber]) {
283-
if(xSizemode === 'pixel') param = x2p(xAnchor) + Number(param);
284-
else param = x2p(param);
330+
if(xSizemode === 'pixel') param = segmentX2p(xAnchor) + Number(param);
331+
else param = segmentX2p(param);
285332
} else if(yParams[paramNumber]) {
286-
if(ySizemode === 'pixel') param = y2p(yAnchor) - Number(param);
287-
else param = y2p(param);
333+
if(ySizemode === 'pixel') param = segmentY2p(yAnchor) - Number(param);
334+
else param = segmentY2p(param);
288335
}
289336
paramNumber++;
290337

@@ -297,6 +344,9 @@ function convertPath(options, x2p, y2p) {
297344
Lib.log('Ignoring extra params in segment ' + segment);
298345
}
299346

347+
if(hasDrawnX) xVertexIndex++;
348+
if(hasDrawnY) yVertexIndex++;
349+
300350
return segmentType + paramString;
301351
});
302352
}

0 commit comments

Comments
 (0)