Skip to content

Fixed size shapes #2532

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

Merged
merged 8 commits into from
Apr 11, 2018
83 changes: 75 additions & 8 deletions src/components/shapes/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,20 @@ module.exports = {
'Specifies the shape type to be drawn.',

'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)',
'with respect to the axes\' sizing mode.',

'If *circle*, a circle is drawn from',
'((`x0`+`x1`)/2, (`y0`+`y1`)/2))',
'with radius',
'(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)',
'with respect to the axes\' sizing mode.',

'If *rect*, a rectangle is drawn linking',
'(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)',
'with respect to the axes\' sizing mode.',

'If *path*, draw a custom SVG path using `path`.'
'If *path*, draw a custom SVG path using `path`.',
'with respect to the axes\' sizing mode.'
].join(' ')
},

Expand All @@ -61,7 +65,7 @@ module.exports = {
description: [
'Sets the shape\'s x coordinate axis.',
'If set to an x axis id (e.g. *x* or *x2*), the `x` position',
'refers to an x coordinate',
'refers to an x coordinate.',
'If set to *paper*, the `x` position refers to the distance from',
'the left side of the plotting area in normalized coordinates',
'where *0* (*1*) corresponds to the left (right) side.',
Expand All @@ -71,13 +75,43 @@ module.exports = {
'the date to unix time in milliseconds.'
].join(' ')
}),
xsizemode: {
valType: 'enumerated',
values: ['scaled', 'pixel'],
dflt: 'scaled',
role: 'info',
editType: 'calcIfAutorange+arraydraw',
description: [
'Sets the shapes\'s sizing mode along the x axis.',
'If set to *scaled*, `x0`, `x1` and x coordinates within `path` refer to',
'data values on the x axis or a fraction of the plot area\'s width',
'(`xref` set to *paper*).',
'If set to *pixel*, `xanchor` specifies the x position in terms',
'of data or plot fraction but `x0`, `x1` and x coordinates within `path`',
'are pixels relative to `xanchor`. This way, the shape can have',
'a fixed width while maintaining a position relative to data or',
'plot fraction.'
].join(' ')
},
xanchor: {
valType: 'any',
role: 'info',
editType: 'calcIfAutorange+arraydraw',
description: [
'Only relevant in conjunction with `xsizemode` set to *pixel*.',
'Specifies the anchor point on the x axis to which `x0`, `x1`',
'and x coordinates within `path` are relative to.',
'E.g. useful to attach a pixel sized shape to a certain data value.',
'No effect when `xsizemode` not set to *pixel*.'
].join(' ')
},
x0: {
valType: 'any',
role: 'info',
editType: 'calcIfAutorange+arraydraw',
description: [
'Sets the shape\'s starting x position.',
'See `type` for more info.'
'See `type` and `xsizemode` for more info.'
].join(' ')
},
x1: {
Expand All @@ -86,7 +120,7 @@ module.exports = {
editType: 'calcIfAutorange+arraydraw',
description: [
'Sets the shape\'s end x position.',
'See `type` for more info.'
'See `type` and `xsizemode` for more info.'
].join(' ')
},

Expand All @@ -100,13 +134,43 @@ module.exports = {
'where *0* (*1*) corresponds to the bottom (top).'
].join(' ')
}),
ysizemode: {
valType: 'enumerated',
values: ['scaled', 'pixel'],
dflt: 'scaled',
role: 'info',
editType: 'calcIfAutorange+arraydraw',
description: [
'Sets the shapes\'s sizing mode along the y axis.',
'If set to *scaled*, `y0`, `y1` and y coordinates within `path` refer to',
'data values on the y axis or a fraction of the plot area\'s height',
'(`yref` set to *paper*).',
'If set to *pixel*, `yanchor` specifies the y position in terms',
'of data or plot fraction but `y0`, `y1` and y coordinates within `path`',
'are pixels relative to `yanchor`. This way, the shape can have',
'a fixed height while maintaining a position relative to data or',
'plot fraction.'
].join(' ')
},
yanchor: {
valType: 'any',
role: 'info',
editType: 'calcIfAutorange+arraydraw',
description: [
'Only relevant in conjunction with `ysizemode` set to *pixel*.',
'Specifies the anchor point on the y axis to which `y0`, `y1`',
'and y coordinates within `path` are relative to.',
'E.g. useful to attach a pixel sized shape to a certain data value.',
'No effect when `ysizemode` not set to *pixel*.'
].join(' ')
},
y0: {
valType: 'any',
role: 'info',
editType: 'calcIfAutorange+arraydraw',
description: [
'Sets the shape\'s starting y position.',
'See `type` for more info.'
'See `type` and `ysizemode` for more info.'
].join(' ')
},
y1: {
Expand All @@ -115,7 +179,7 @@ module.exports = {
editType: 'calcIfAutorange+arraydraw',
description: [
'Sets the shape\'s end y position.',
'See `type` for more info.'
'See `type` and `ysizemode` for more info.'
].join(' ')
},

Expand All @@ -124,8 +188,11 @@ module.exports = {
role: 'info',
editType: 'calcIfAutorange+arraydraw',
description: [
'For `type` *path* - a valid SVG path but with the pixel values',
'replaced by data values. There are a few restrictions / quirks',
'For `type` *path* - a valid SVG path with the pixel values',
'replaced by data values in `xsizemode`/`ysizemode` being *scaled*',
'and taken unmodified as pixels relative to `xanchor` and `yanchor`',
'in case of *pixel* size mode.',
'There are a few restrictions / quirks',
'only absolute instructions, not relative. So the allowed segments',
'are: M, L, H, V, Q, C, T, S, and Z',
'arcs (A) are not allowed because radius rx and ry are relative.',
Expand Down
66 changes: 60 additions & 6 deletions src/components/shapes/calc_autorange.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,79 @@ module.exports = function calcAutorange(gd) {
if(!shapeList.length || !gd._fullData.length) return;

for(var i = 0; i < shapeList.length; i++) {
var shape = shapeList[i],
ppad = shape.line.width / 2;
var shape = shapeList[i];

var ax, bounds;

if(shape.xref !== 'paper') {
var vx0 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x0,
vx1 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x1;
ax = Axes.getFromId(gd, shape.xref);
bounds = shapeBounds(ax, shape.x0, shape.x1, shape.path, constants.paramIsX);
if(bounds) Axes.expand(ax, bounds, {ppad: ppad});

bounds = shapeBounds(ax, vx0, vx1, shape.path, constants.paramIsX);

if(bounds) Axes.expand(ax, bounds, calcXPaddingOptions(shape));
}

if(shape.yref !== 'paper') {
var vy0 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y0,
vy1 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y1;
ax = Axes.getFromId(gd, shape.yref);
bounds = shapeBounds(ax, shape.y0, shape.y1, shape.path, constants.paramIsY);
if(bounds) Axes.expand(ax, bounds, {ppad: ppad});

bounds = shapeBounds(ax, vy0, vy1, shape.path, constants.paramIsY);
if(bounds) Axes.expand(ax, bounds, calcYPaddingOptions(shape));
}
}
};

function calcXPaddingOptions(shape) {
return calcPaddingOptions(shape.line.width, shape.xsizemode, shape.x0, shape.x1, shape.path, false);
}

function calcYPaddingOptions(shape) {
return calcPaddingOptions(shape.line.width, shape.ysizemode, shape.y0, shape.y1, shape.path, true);
}

function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) {
var ppad = lineWidth / 2,
axisDirectionReverted = isYAxis;

if(sizeMode === 'pixel') {
var coords = path ?
extractPathCoords(path, isYAxis ? constants.paramIsY : constants.paramIsX) :
[v0, v1];
var maxValue = Lib.aggNums(Math.max, null, coords),
minValue = Lib.aggNums(Math.min, null, coords),
beforePad = minValue < 0 ? Math.abs(minValue) + ppad : ppad,
afterPad = maxValue > 0 ? maxValue + ppad : ppad;

return {
ppad: ppad,
ppadplus: axisDirectionReverted ? beforePad : afterPad,
ppadminus: axisDirectionReverted ? afterPad : beforePad
};
} else {
return {ppad: ppad};
}
}

function extractPathCoords(path, paramsToUse) {
var extractedCoordinates = [];

var segments = path.match(constants.segmentRE);
segments.forEach(function(segment) {
var relevantParamIdx = paramsToUse[segment.charAt(0)].drawn;
if(relevantParamIdx === undefined) return;

var params = segment.substr(1).match(constants.paramRE);
if(!params || params.length < relevantParamIdx) return;

extractedCoordinates.push(params[relevantParamIdx]);
});

return extractedCoordinates;
}

function shapeBounds(ax, v0, v1, path, paramsToUse) {
var convertVal = (ax.type === 'category') ? ax.r2c : ax.d2c;

Expand Down
Loading