Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e980e44
Linting/formatting
camdecoster Sep 29, 2025
212f892
Return empty string for undefined value in templateFormatString
camdecoster Sep 29, 2025
75b157f
Refactoring
camdecoster Oct 2, 2025
5f3670f
Add fallback value for template strings
camdecoster Oct 3, 2025
336e9ae
Add fallback to calls to hovertemplateString
camdecoster Oct 3, 2025
02a4cad
Add fallback to calls to texttemplateString
camdecoster Oct 3, 2025
77bc2b9
Add fallback to calls to texttemplateStringForShapes
camdecoster Oct 7, 2025
85b67e9
Update defaults calculations
camdecoster Oct 6, 2025
680c793
Add helper function for template fallback attributes
camdecoster Oct 6, 2025
52c29cc
Add fallback value to attributes files
camdecoster Oct 7, 2025
f623b22
Update esbuild strip meta plugin to handle more joined arrays
camdecoster Oct 8, 2025
074f8a0
Return array from ternary
camdecoster Oct 8, 2025
6f3daa8
Update tests per default fallback value
camdecoster Oct 8, 2025
6719bd3
Update schema
camdecoster Oct 8, 2025
84fc044
Update test baselines
camdecoster Oct 8, 2025
e719f74
Rename object keys
camdecoster Oct 8, 2025
fb7a40c
Add/update tests to check fallback value
camdecoster Oct 9, 2025
faaae28
Add draftlog
camdecoster Oct 9, 2025
5c032c6
Fix typos
camdecoster Oct 21, 2025
a0ce641
Update fallback `editType` to match template
camdecoster Oct 21, 2025
14ab31c
Handle undefined values and missing values differently
camdecoster Oct 23, 2025
4f79efe
Update default fallback value and template attribute descriptions
camdecoster Oct 23, 2025
257475c
Add tests for missing and undefined values
camdecoster Oct 23, 2025
a148edd
Update schema
camdecoster Oct 23, 2025
c40fe9f
Merge remote-tracking branch 'origin/master' into cam/7564/return-emp…
camdecoster Oct 23, 2025
6a66148
Revert "Update test baselines"
camdecoster Oct 23, 2025
5215f13
Always use fallback for missing values, except when fallback is false
camdecoster Oct 24, 2025
30f87f3
Update schema
camdecoster Oct 24, 2025
b61d70d
Update mocks to show fallback values
camdecoster Oct 24, 2025
d900ca3
Update tests per final behavior
camdecoster Oct 24, 2025
a061787
Update baseline images
camdecoster Oct 24, 2025
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
Next Next commit
Linting/formatting
  • Loading branch information
camdecoster committed Oct 9, 2025
commit e980e44ed2c1925f062d39f82a97a8a0f9b9ba88
998 changes: 536 additions & 462 deletions src/components/drawing/index.js

Large diffs are not rendered by default.

1,109 changes: 611 additions & 498 deletions src/components/fx/hover.js

Large diffs are not rendered by default.

97 changes: 43 additions & 54 deletions src/components/shapes/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ module.exports = templatedArray('shape', {
valType: 'boolean',
dflt: false,
editType: 'calc+arraydraw',
description: [
'Determines whether or not this',
'shape is shown in the legend.'
].join(' ')
description: ['Determines whether or not this', 'shape is shown in the legend.'].join(' ')
},

legend: extendFlat({}, basePlotAttributes.legend, {
Expand Down Expand Up @@ -57,11 +54,9 @@ module.exports = templatedArray('shape', {
}),
font: fontAttrs({
editType: 'calc+arraydraw',
description: [
'Sets this legend group\'s title font.'
].join(' '),
description: ["Sets this legend group's title font."].join(' ')
}),
editType: 'calc+arraydraw',
editType: 'calc+arraydraw'
},

legendrank: extendFlat({}, basePlotAttributes.legendrank, {
Expand All @@ -80,7 +75,7 @@ module.exports = templatedArray('shape', {

legendwidth: extendFlat({}, basePlotAttributes.legendwidth, {
editType: 'calc+arraydraw',
description: 'Sets the width (in px or fraction) of the legend for this shape.',
description: 'Sets the width (in px or fraction) of the legend for this shape.'
}),

type: {
Expand All @@ -91,20 +86,20 @@ module.exports = templatedArray('shape', {
'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.',
"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.',
"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.',
"with respect to the axes' sizing mode.",

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

Expand All @@ -121,7 +116,7 @@ module.exports = templatedArray('shape', {

xref: extendFlat({}, annAttrs.xref, {
description: [
'Sets the shape\'s x coordinate axis.',
"Sets the shape's x coordinate axis.",
axisPlaceableObjs.axisRefDescription('x', 'left', 'right')
].join(' ')
}),
Expand All @@ -131,9 +126,9 @@ module.exports = templatedArray('shape', {
dflt: 'scaled',
editType: 'calc+arraydraw',
description: [
'Sets the shapes\'s sizing mode along the x axis.',
"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',
"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`',
Expand All @@ -156,18 +151,12 @@ module.exports = templatedArray('shape', {
x0: {
valType: 'any',
editType: 'calc+arraydraw',
description: [
'Sets the shape\'s starting x position.',
'See `type` and `xsizemode` for more info.'
].join(' ')
description: ["Sets the shape's starting x position.", 'See `type` and `xsizemode` for more info.'].join(' ')
},
x1: {
valType: 'any',
editType: 'calc+arraydraw',
description: [
'Sets the shape\'s end x position.',
'See `type` and `xsizemode` for more info.'
].join(' ')
description: ["Sets the shape's end x position.", 'See `type` and `xsizemode` for more info.'].join(' ')
},
x0shift: {
valType: 'number',
Expand Down Expand Up @@ -195,7 +184,7 @@ module.exports = templatedArray('shape', {
},
yref: extendFlat({}, annAttrs.yref, {
description: [
'Sets the shape\'s y coordinate axis.',
"Sets the shape's y coordinate axis.",
axisPlaceableObjs.axisRefDescription('y', 'bottom', 'top')
].join(' ')
}),
Expand All @@ -205,9 +194,9 @@ module.exports = templatedArray('shape', {
dflt: 'scaled',
editType: 'calc+arraydraw',
description: [
'Sets the shapes\'s sizing mode along the y axis.',
"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',
"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`',
Expand All @@ -230,18 +219,12 @@ module.exports = templatedArray('shape', {
y0: {
valType: 'any',
editType: 'calc+arraydraw',
description: [
'Sets the shape\'s starting y position.',
'See `type` and `ysizemode` for more info.'
].join(' ')
description: ["Sets the shape's starting y position.", 'See `type` and `ysizemode` for more info.'].join(' ')
},
y1: {
valType: 'any',
editType: 'calc+arraydraw',
description: [
'Sets the shape\'s end y position.',
'See `type` and `ysizemode` for more info.'
].join(' ')
description: ["Sets the shape's end y position.", 'See `type` and `ysizemode` for more info.'].join(' ')
},
y0shift: {
valType: 'number',
Expand Down Expand Up @@ -291,8 +274,8 @@ module.exports = templatedArray('shape', {
'of categories because using the categories themselves there would',
'be no way to describe fractional positions',
'On data axes: because space and T are both normal components of path',
'strings, we can\'t use either to separate date from time parts.',
'Therefore we\'ll use underscore for this purpose:',
"strings, we can't use either to separate date from time parts.",
"Therefore we'll use underscore for this purpose:",
'2015-02-21_13:45:56.789'
].join(' ')
},
Expand All @@ -306,18 +289,16 @@ module.exports = templatedArray('shape', {
description: 'Sets the opacity of the shape.'
},
line: {
color: extendFlat({}, scatterLineAttrs.color, {editType: 'arraydraw'}),
width: extendFlat({}, scatterLineAttrs.width, {editType: 'calc+arraydraw'}),
dash: extendFlat({}, dash, {editType: 'arraydraw'}),
color: extendFlat({}, scatterLineAttrs.color, { editType: 'arraydraw' }),
width: extendFlat({}, scatterLineAttrs.width, { editType: 'calc+arraydraw' }),
dash: extendFlat({}, dash, { editType: 'arraydraw' }),
editType: 'calc+arraydraw'
},
fillcolor: {
valType: 'color',
dflt: 'rgba(0,0,0,0)',
editType: 'arraydraw',
description: [
'Sets the color filling the shape\'s interior. Only applies to closed shapes.'
].join(' ')
description: ["Sets the color filling the shape's interior. Only applies to closed shapes."].join(' ')
},
fillrule: {
valType: 'enumerated',
Expand Down Expand Up @@ -349,7 +330,7 @@ module.exports = templatedArray('shape', {
'It is also used for legend item if `name` is not provided.'
].join(' ')
},
texttemplate: shapeTexttemplateAttrs({}, {keys: Object.keys(shapeLabelTexttemplateVars)}),
texttemplate: shapeTexttemplateAttrs({}, { keys: Object.keys(shapeLabelTexttemplateVars) }),
font: fontAttrs({
editType: 'calc+arraydraw',
colorEditType: 'arraydraw',
Expand All @@ -358,10 +339,18 @@ module.exports = templatedArray('shape', {
textposition: {
valType: 'enumerated',
values: [
'top left', 'top center', 'top right',
'middle left', 'middle center', 'middle right',
'bottom left', 'bottom center', 'bottom right',
'start', 'middle', 'end',
'top left',
'top center',
'top right',
'middle left',
'middle center',
'middle right',
'bottom left',
'bottom center',
'bottom right',
'start',
'middle',
'end'
],
editType: 'arraydraw',
description: [
Expand All @@ -371,7 +360,7 @@ module.exports = templatedArray('shape', {
'*middle center*, *middle right*, *bottom left*, *bottom center*,',
'and *bottom right*.',
'Supported values for lines are *start*, *middle*, and *end*.',
'Default: *middle center* for rectangles, circles, and paths; *middle* for lines.',
'Default: *middle center* for rectangles, circles, and paths; *middle* for lines.'
].join(' ')
},
textangle: {
Expand All @@ -391,27 +380,27 @@ module.exports = templatedArray('shape', {
dflt: 'auto',
editType: 'calc+arraydraw',
description: [
'Sets the label\'s horizontal position anchor',
"Sets the label's horizontal position anchor",
'This anchor binds the specified `textposition` to the *left*, *center*',
'or *right* of the label text.',
'For example, if `textposition` is set to *top right* and',
'`xanchor` to *right* then the right-most portion of the',
'label text lines up with the right-most edge of the',
'shape.',
].join(' '),
'shape.'
].join(' ')
},
yanchor: {
valType: 'enumerated',
values: ['top', 'middle', 'bottom'],
editType: 'calc+arraydraw',
description: [
'Sets the label\'s vertical position anchor',
"Sets the label's vertical position anchor",
'This anchor binds the specified `textposition` to the *top*, *middle*',
'or *bottom* of the label text.',
'For example, if `textposition` is set to *top right* and',
'`yanchor` to *top* then the top-most portion of the',
'label text lines up with the top-most edge of the',
'shape.',
'shape.'
].join(' ')
},
padding: {
Expand Down
48 changes: 27 additions & 21 deletions src/components/shapes/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ var handleArrayContainerDefaults = require('../../plots/array_container_defaults
var attributes = require('./attributes');
var helpers = require('./helpers');


module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
handleArrayContainerDefaults(layoutIn, layoutOut, {
name: 'shapes',
Expand All @@ -19,9 +18,13 @@ function dfltLabelYanchor(isLine, labelTextPosition) {
// If shape is a line, default y-anchor is 'bottom' (so that text is above line by default)
// Otherwise, default y-anchor is equal to y-component of `textposition`
// (so that text is positioned inside shape bounding box by default)
return isLine ? 'bottom' :
labelTextPosition.indexOf('top') !== -1 ? 'top' :
labelTextPosition.indexOf('bottom') !== -1 ? 'bottom' : 'middle';
return isLine
? 'bottom'
: labelTextPosition.indexOf('top') !== -1
? 'top'
: labelTextPosition.indexOf('bottom') !== -1
? 'bottom'
: 'middle';
}

function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
Expand All @@ -32,10 +35,10 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
shapeOut._isShape = true;

var visible = coerce('visible');
if(!visible) return;
if (!visible) return;

var showlegend = coerce('showlegend');
if(showlegend) {
if (showlegend) {
coerce('legend');
coerce('legendwidth');
coerce('legendgroup');
Expand All @@ -48,15 +51,15 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
var dfltType = path ? 'path' : 'rect';
var shapeType = coerce('type', dfltType);
var noPath = shapeType !== 'path';
if(noPath) delete shapeOut.path;
if (noPath) delete shapeOut.path;

coerce('editable');
coerce('layer');
coerce('opacity');
coerce('fillcolor');
coerce('fillrule');
var lineWidth = coerce('line.width');
if(lineWidth) {
if (lineWidth) {
coerce('line.color');
coerce('line.dash');
}
Expand All @@ -66,26 +69,25 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {

// positioning
var axLetters = ['x', 'y'];
for(var i = 0; i < 2; i++) {
for (var i = 0; i < 2; i++) {
var axLetter = axLetters[i];
var attrAnchor = axLetter + 'anchor';
var sizeMode = axLetter === 'x' ? xSizeMode : ySizeMode;
var gdMock = {_fullLayout: fullLayout};
var gdMock = { _fullLayout: fullLayout };
var ax;
var pos2r;
var r2pos;

// xref, yref
var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined,
'paper');
var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, 'paper');
var axRefType = Axes.getRefType(axRef);

if(axRefType === 'range') {
if (axRefType === 'range') {
ax = Axes.getFromId(gdMock, axRef);
ax._shapeIndices.push(shapeOut._index);
r2pos = helpers.rangeToShapePosition(ax);
pos2r = helpers.shapePositionToRange(ax);
if(ax.type === 'category' || ax.type === 'multicategory') {
if (ax.type === 'category' || ax.type === 'multicategory') {
coerce(axLetter + '0shift');
coerce(axLetter + '1shift');
}
Expand All @@ -94,7 +96,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
}

// Coerce x0, x1, y0, y1
if(noPath) {
if (noPath) {
var dflt0 = 0.25;
var dflt1 = 0.75;

Expand All @@ -109,7 +111,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
shapeIn[attr0] = pos2r(shapeIn[attr0], true);
shapeIn[attr1] = pos2r(shapeIn[attr1], true);

if(sizeMode === 'pixel') {
if (sizeMode === 'pixel') {
coerce(attr0, 0);
coerce(attr1, 10);
} else {
Expand All @@ -125,7 +127,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
}

// Coerce xanchor and yanchor
if(sizeMode === 'pixel') {
if (sizeMode === 'pixel') {
// Hack for log axis described above
var inAnchor = shapeIn[attrAnchor];
shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true);
Expand All @@ -138,16 +140,20 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
}
}

if(noPath) {
if (noPath) {
Lib.noneOrAll(shapeIn, shapeOut, ['x0', 'x1', 'y0', 'y1']);
}

// Label options
var isLine = shapeType === 'line';
var labelTextTemplate, labelText;
if(noPath) { labelTextTemplate = coerce('label.texttemplate'); }
if(!labelTextTemplate) { labelText = coerce('label.text'); }
if(labelText || labelTextTemplate) {
if (noPath) {
labelTextTemplate = coerce('label.texttemplate');
}
if (!labelTextTemplate) {
labelText = coerce('label.text');
}
if (labelText || labelTextTemplate) {
coerce('label.textangle');
var labelTextPosition = coerce('label.textposition', isLine ? 'middle' : 'middle center');
coerce('label.xanchor');
Expand Down
Loading