diff --git a/draftlogs/6454_add.md b/draftlogs/6454_add.md
new file mode 100644
index 00000000000..110e69f65c5
--- /dev/null
+++ b/draftlogs/6454_add.md
@@ -0,0 +1,2 @@
+ - Add `label` attribute to shapes [[#6454](https://github.com/plotly/plotly.js/pull/6454)], with thanks to
+the [Volkswagen](https://www.volkswagenag.com) Center of Excellence for Battery Cells for sponsoring development!
\ No newline at end of file
diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js
index 208c8b0b938..63aa765f046 100644
--- a/src/components/shapes/attributes.js
+++ b/src/components/shapes/attributes.js
@@ -1,6 +1,7 @@
'use strict';
var annAttrs = require('../annotations/attributes');
+var fontAttrs = require('../../plots/font_attributes');
var scatterLineAttrs = require('../../traces/scatter/attributes').line;
var dash = require('../drawing/attributes').dash;
var extendFlat = require('../../lib/extend').extendFlat;
@@ -224,6 +225,85 @@ module.exports = templatedArray('shape', {
'`config.editable` or `config.edits.shapePosition`.'
].join(' ')
},
-
+ label: {
+ text: {
+ valType: 'string',
+ dflt: '',
+ editType: 'arraydraw',
+ description: 'Sets the text to display with shape.'
+ },
+ font: fontAttrs({
+ editType: 'calc+arraydraw',
+ colorEditType: 'arraydraw',
+ description: 'Sets the shape label text font.'
+ }),
+ 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',
+ ],
+ editType: 'arraydraw',
+ description: [
+ 'Sets the position of the label text relative to the shape.',
+ 'Supported values for rectangles, circles and paths are',
+ '*top left*, *top center*, *top right*, *middle left*,',
+ '*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.',
+ ].join(' ')
+ },
+ textangle: {
+ valType: 'angle',
+ dflt: 'auto',
+ editType: 'calc+arraydraw',
+ description: [
+ 'Sets the angle at which the label text is drawn',
+ 'with respect to the horizontal. For lines, angle *auto*',
+ 'is the same angle as the line. For all other shapes,',
+ 'angle *auto* is horizontal.'
+ ].join(' ')
+ },
+ xanchor: {
+ valType: 'enumerated',
+ values: ['auto', 'left', 'center', 'right'],
+ dflt: 'auto',
+ editType: 'calc+arraydraw',
+ description: [
+ '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(' '),
+ },
+ yanchor: {
+ valType: 'enumerated',
+ values: ['top', 'middle', 'bottom'],
+ editType: 'calc+arraydraw',
+ description: [
+ '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.',
+ ].join(' ')
+ },
+ padding: {
+ valType: 'number',
+ dflt: 3,
+ min: 0,
+ editType: 'arraydraw',
+ description: 'Sets padding (in px) between edge of label and edge of shape.'
+ },
+ editType: 'arraydraw'
+ },
editType: 'arraydraw'
});
diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js
index fb757386a08..79163ae2d09 100644
--- a/src/components/shapes/defaults.js
+++ b/src/components/shapes/defaults.js
@@ -15,6 +15,15 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
});
};
+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';
+}
+
function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
function coerce(attr, dflt) {
return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt);
@@ -116,4 +125,16 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
if(noPath) {
Lib.noneOrAll(shapeIn, shapeOut, ['x0', 'x1', 'y0', 'y1']);
}
+
+ // Label options
+ var isLine = shapeType === 'line';
+ var labelText = coerce('label.text');
+ if(labelText) {
+ coerce('label.textangle');
+ var labelTextPosition = coerce('label.textposition', isLine ? 'middle' : 'middle center');
+ coerce('label.xanchor');
+ coerce('label.yanchor', dfltLabelYanchor(isLine, labelTextPosition));
+ coerce('label.padding');
+ Lib.coerceFont(coerce, 'label.font', fullLayout.font);
+ }
}
diff --git a/src/components/shapes/display_outlines.js b/src/components/shapes/display_outlines.js
index deae80a5291..6f19980ed82 100644
--- a/src/components/shapes/display_outlines.js
+++ b/src/components/shapes/display_outlines.js
@@ -36,7 +36,7 @@ module.exports = function displayOutlines(polygons, outlines, dragOptions, nCall
// recursive call
displayOutlines(polygons, outlines, dragOptions, nCalls++);
- if(pointsOnEllipse(polygons[0])) {
+ if(pointsOnEllipse(polygons[0]) || dragOptions.hasText) {
update({redrawing: true});
}
}
diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js
index 03e42e92528..6c61f68c960 100644
--- a/src/components/shapes/draw.js
+++ b/src/components/shapes/draw.js
@@ -1,5 +1,7 @@
'use strict';
+var d3 = require('@plotly/d3');
+
var Registry = require('../../registry');
var Lib = require('../../lib');
var Axes = require('../../plots/cartesian/axes');
@@ -16,9 +18,12 @@ var arrayEditor = require('../../plot_api/plot_template').arrayEditor;
var dragElement = require('../dragelement');
var setCursor = require('../../lib/setcursor');
+var svgTextUtils = require('../../lib/svg_text_utils');
+
var constants = require('./constants');
var helpers = require('./helpers');
var getPathString = helpers.getPathString;
+var FROM_TL = require('../../constants/alignment').FROM_TL;
// Shapes are stored in gd.layout.shapes, an array of objects
@@ -42,10 +47,15 @@ function draw(gd) {
// Remove previous shapes before drawing new in shapes in fullLayout.shapes
fullLayout._shapeUpperLayer.selectAll('path').remove();
fullLayout._shapeLowerLayer.selectAll('path').remove();
+ fullLayout._shapeUpperLayer.selectAll('text').remove();
+ fullLayout._shapeLowerLayer.selectAll('text').remove();
for(var k in fullLayout._plots) {
var shapelayer = fullLayout._plots[k].shapelayer;
- if(shapelayer) shapelayer.selectAll('path').remove();
+ if(shapelayer) {
+ shapelayer.selectAll('path').remove();
+ shapelayer.selectAll('text').remove();
+ }
}
for(var i = 0; i < fullLayout.shapes.length; i++) {
@@ -129,14 +139,21 @@ function drawOne(gd, index) {
opacity = gd._fullLayout.activeshape.opacity;
}
- var path = shapeLayer.append('path')
+ var shapeGroup = shapeLayer.append('g')
+ .classed('shape-group', true)
+ .attr({ 'data-index': index });
+
+ var path = shapeGroup.append('path')
.attr(attrs)
.style('opacity', opacity)
.call(Color.stroke, lineColor)
.call(Color.fill, fillColor)
.call(Drawing.dashLine, lineDash, lineWidth);
- setClipPath(path, gd, options);
+ setClipPath(shapeGroup, gd, options);
+
+ // Draw or clear the label
+ drawLabel(gd, index, options, shapeGroup);
var editHelpers;
if(isActiveShape || gd._context.edits.shapePosition) editHelpers = arrayEditor(gd.layout, 'shapes', options);
@@ -151,6 +168,7 @@ function drawOne(gd, index) {
plotinfo: plotinfo,
gd: gd,
editHelpers: editHelpers,
+ hasText: options.label.text,
isActiveShape: true // i.e. to enable controllers
};
@@ -166,7 +184,6 @@ function drawOne(gd, index) {
);
}
}
-
path.node().addEventListener('click', function() { return activateShape(gd, path); });
}
}
@@ -202,6 +219,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
var n0, s0, w0, e0, optN, optS, optW, optE;
var pathIn;
+ var shapeGroup = d3.select(shapePath.node().parentNode);
+
// setup conversion functions
var xa = Axes.getFromId(gd, shapeOptions.xref);
var xRefType = Axes.getRefType(shapeOptions.xref);
@@ -238,7 +257,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
// Note that by setting the `data-index` attr, it is ensured that
// the helper group is purged in this modules `draw` function
var g = shapeLayer.append('g')
- .attr('data-index', index);
+ .attr('data-index', index)
+ .attr('drag-helper', true);
// Helper path for moving
g.append('path')
@@ -423,6 +443,7 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
shapePath.attr('d', getPathString(gd, shapeOptions));
renderVisualCues(shapeLayer, shapeOptions);
+ drawLabel(gd, index, shapeOptions, shapeGroup);
}
function resizeShape(dx, dy) {
@@ -495,6 +516,7 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
shapePath.attr('d', getPathString(gd, shapeOptions));
renderVisualCues(shapeLayer, shapeOptions);
+ drawLabel(gd, index, shapeOptions, shapeGroup);
}
function renderVisualCues(shapeLayer, shapeOptions) {
@@ -579,6 +601,240 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHe
}
}
+function drawLabel(gd, index, options, shapeGroup) {
+ // Remove existing label
+ shapeGroup.selectAll('.shape-label').remove();
+
+ // If no label, return
+ if(!options.label.text) return;
+
+ var labelGroupAttrs = {
+ 'data-index': index,
+ };
+ var text = options.label.text;
+ var font = options.label.font;
+
+ var labelTextAttrs = {
+ 'data-notex': 1
+ };
+
+ var labelGroup = shapeGroup.append('g')
+ .attr(labelGroupAttrs)
+ .classed('shape-label', true);
+ var labelText = labelGroup.append('text')
+ .attr(labelTextAttrs)
+ .classed('shape-label-text', true)
+ .text(text);
+
+ // Get x and y bounds of shape
+ var shapex0, shapex1, shapey0, shapey1;
+ if(options.path) {
+ // If shape is defined as a path, get the
+ // min and max bounds across all polygons in path
+ var d = getPathString(gd, options);
+ var polygons = readPaths(d, gd);
+ shapex0 = Infinity;
+ shapey0 = Infinity;
+ shapex1 = -Infinity;
+ shapey1 = -Infinity;
+ for(var i = 0; i < polygons.length; i++) {
+ for(var j = 0; j < polygons[i].length; j++) {
+ var p = polygons[i][j];
+ for(var k = 1; k < p.length; k += 2) {
+ var _x = p[k];
+ var _y = p[k + 1];
+
+ shapex0 = Math.min(shapex0, _x);
+ shapex1 = Math.max(shapex1, _x);
+ shapey0 = Math.min(shapey0, _y);
+ shapey1 = Math.max(shapey1, _y);
+ }
+ }
+ }
+ } else {
+ // Otherwise, we use the x and y bounds defined in the shape options
+ // and convert them to pixel coordinates
+ // Setup conversion functions
+ var xa = Axes.getFromId(gd, options.xref);
+ var xRefType = Axes.getRefType(options.xref);
+ var ya = Axes.getFromId(gd, options.yref);
+ 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);
+ }
+
+ // Handle `auto` angle
+ var textangle = options.label.textangle;
+ if(textangle === 'auto') {
+ if(options.type === 'line') {
+ // Auto angle for line is same angle as line
+ textangle = calcTextAngle(shapex0, shapey0, shapex1, shapey1);
+ } else {
+ // Auto angle for all other shapes is 0
+ textangle = 0;
+ }
+ }
+
+ // Do an initial render so we can get the text bounding box height
+ labelText.call(function(s) {
+ s.call(Drawing.font, font).attr({});
+ svgTextUtils.convertToTspans(s, gd);
+ return s;
+ });
+ var textBB = Drawing.bBox(labelText.node());
+
+ // Calculate correct (x,y) for text
+ // We also determine true xanchor since xanchor depends on position when set to 'auto'
+ var textPos = calcTextPosition(shapex0, shapey0, shapex1, shapey1, options, textangle, textBB);
+ var textx = textPos.textx;
+ var texty = textPos.texty;
+ var xanchor = textPos.xanchor;
+
+ // Update (x,y) position, xanchor, and angle
+ labelText.attr({
+ 'text-anchor': {
+ left: 'start',
+ center: 'middle',
+ right: 'end'
+ }[xanchor],
+ y: texty,
+ x: textx,
+ transform: 'rotate(' + textangle + ',' + textx + ',' + texty + ')'
+ }).call(svgTextUtils.positionText, textx, texty);
+}
+
+function calcTextAngle(shapex0, shapey0, shapex1, shapey1) {
+ var dy, dx;
+ dx = Math.abs(shapex1 - shapex0);
+ if(shapex1 >= shapex0) {
+ dy = shapey0 - shapey1;
+ } else {
+ dy = shapey1 - shapey0;
+ }
+ return -180 / Math.PI * Math.atan2(dy, dx);
+}
+
+function calcTextPosition(shapex0, shapey0, shapex1, shapey1, shapeOptions, actualTextAngle, textBB) {
+ var textPosition = shapeOptions.label.textposition;
+ var textAngle = shapeOptions.label.textangle;
+ var textPadding = shapeOptions.label.padding;
+ var shapeType = shapeOptions.type;
+ var textAngleRad = Math.PI / 180 * actualTextAngle;
+ var sinA = Math.sin(textAngleRad);
+ var cosA = Math.cos(textAngleRad);
+ var xanchor = shapeOptions.label.xanchor;
+ var yanchor = shapeOptions.label.yanchor;
+
+ var textx, texty, paddingX, paddingY;
+
+ // Text position functions differently for lines vs. other shapes
+ if(shapeType === 'line') {
+ // Set base position for start vs. center vs. end of line (default is 'center')
+ if(textPosition === 'start') {
+ textx = shapex0;
+ texty = shapey0;
+ } else if(textPosition === 'end') {
+ textx = shapex1;
+ texty = shapey1;
+ } else { // Default: center
+ textx = (shapex0 + shapex1) / 2;
+ texty = (shapey0 + shapey1) / 2;
+ }
+
+ // Set xanchor if xanchor is 'auto'
+ if(xanchor === 'auto') {
+ if(textPosition === 'start') {
+ if(textAngle === 'auto') {
+ if(shapex1 > shapex0) xanchor = 'left';
+ else if(shapex1 < shapex0) xanchor = 'right';
+ else xanchor = 'center';
+ } else {
+ if(shapex1 > shapex0) xanchor = 'right';
+ else if(shapex1 < shapex0) xanchor = 'left';
+ else xanchor = 'center';
+ }
+ } else if(textPosition === 'end') {
+ if(textAngle === 'auto') {
+ if(shapex1 > shapex0) xanchor = 'right';
+ else if(shapex1 < shapex0) xanchor = 'left';
+ else xanchor = 'center';
+ } else {
+ if(shapex1 > shapex0) xanchor = 'left';
+ else if(shapex1 < shapex0) xanchor = 'right';
+ else xanchor = 'center';
+ }
+ } else {
+ xanchor = 'center';
+ }
+ }
+
+ // Special case for padding when angle is 'auto' for lines
+ // Padding should be treated as an orthogonal offset in this case
+ // Otherwise, padding is just a simple x and y offset
+ var paddingConstantsX = { left: 1, center: 0, right: -1 };
+ var paddingConstantsY = { bottom: -1, middle: 0, top: 1 };
+ if(textAngle === 'auto') {
+ // Set direction to apply padding (based on `yanchor` only)
+ var paddingDirection = paddingConstantsY[yanchor];
+ paddingX = -textPadding * sinA * paddingDirection;
+ paddingY = textPadding * cosA * paddingDirection;
+ } else {
+ // Set direction to apply padding (based on `xanchor` and `yanchor`)
+ var paddingDirectionX = paddingConstantsX[xanchor];
+ var paddingDirectionY = paddingConstantsY[yanchor];
+ paddingX = textPadding * paddingDirectionX;
+ paddingY = textPadding * paddingDirectionY;
+ }
+ textx = textx + paddingX;
+ texty = texty + paddingY;
+ } else {
+ // Text position for shapes that are not lines
+ // calc horizontal position
+ // Horizontal needs a little extra padding to look balanced
+ paddingX = textPadding + 3;
+ if(textPosition.indexOf('right') !== -1) {
+ textx = Math.max(shapex0, shapex1) - paddingX;
+ if(xanchor === 'auto') xanchor = 'right';
+ } else if(textPosition.indexOf('left') !== -1) {
+ textx = Math.min(shapex0, shapex1) + paddingX;
+ if(xanchor === 'auto') xanchor = 'left';
+ } else { // Default: center
+ textx = (shapex0 + shapex1) / 2;
+ if(xanchor === 'auto') xanchor = 'center';
+ }
+
+ // calc vertical position
+ if(textPosition.indexOf('top') !== -1) {
+ texty = Math.min(shapey0, shapey1);
+ } else if(textPosition.indexOf('bottom') !== -1) {
+ texty = Math.max(shapey0, shapey1);
+ } else {
+ texty = (shapey0 + shapey1) / 2;
+ }
+ // Apply padding
+ paddingY = textPadding;
+ if(yanchor === 'bottom') {
+ texty = texty - paddingY;
+ } else if(yanchor === 'top') {
+ texty = texty + paddingY;
+ }
+ }
+
+ // Shift vertical (& horizontal) position according to `yanchor`
+ var shiftFraction = FROM_TL[yanchor];
+ // Adjust so that text is anchored at top of first line rather than at baseline of first line
+ var baselineAdjust = shapeOptions.label.font.size;
+ var textHeight = textBB.height;
+ var xshift = (textHeight * shiftFraction - baselineAdjust) * sinA;
+ var yshift = -(textHeight * shiftFraction - baselineAdjust) * cosA;
+
+ return { textx: textx + xshift, texty: texty + yshift, xanchor: xanchor };
+}
+
function movePath(pathIn, moveX, moveY) {
return pathIn.replace(constants.segmentRE, function(segment) {
var paramNumber = 0;
diff --git a/src/components/shapes/draw_newshape/attributes.js b/src/components/shapes/draw_newshape/attributes.js
index 9048dce5b58..55fe1eef890 100644
--- a/src/components/shapes/draw_newshape/attributes.js
+++ b/src/components/shapes/draw_newshape/attributes.js
@@ -1,5 +1,6 @@
'use strict';
+var fontAttrs = require('../../../plots/font_attributes');
var dash = require('../../drawing/attributes').dash;
var extendFlat = require('../../../lib/extend').extendFlat;
@@ -78,7 +79,85 @@ module.exports = {
'*vertical* allows vertical extend.'
].join(' ')
},
-
+ label: {
+ text: {
+ valType: 'string',
+ dflt: '',
+ editType: 'none',
+ description: 'Sets the text to display with the new shape.'
+ },
+ font: fontAttrs({
+ editType: 'none',
+ description: 'Sets the new shape label text font.'
+ }),
+ 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',
+ ],
+ editType: 'none',
+ description: [
+ 'Sets the position of the label text relative to the new shape.',
+ 'Supported values for rectangles, circles and paths are',
+ '*top left*, *top center*, *top right*, *middle left*,',
+ '*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.',
+ ].join(' ')
+ },
+ textangle: {
+ valType: 'angle',
+ dflt: 'auto',
+ editType: 'none',
+ description: [
+ 'Sets the angle at which the label text is drawn',
+ 'with respect to the horizontal. For lines, angle *auto*',
+ 'is the same angle as the line. For all other shapes,',
+ 'angle *auto* is horizontal.'
+ ].join(' ')
+ },
+ xanchor: {
+ valType: 'enumerated',
+ values: ['auto', 'left', 'center', 'right'],
+ dflt: 'auto',
+ editType: 'none',
+ description: [
+ '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',
+ 'new shape.',
+ ].join(' '),
+ },
+ yanchor: {
+ valType: 'enumerated',
+ values: ['top', 'middle', 'bottom'],
+ editType: 'none',
+ description: [
+ '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',
+ 'new shape.',
+ ].join(' ')
+ },
+ padding: {
+ valType: 'number',
+ dflt: 3,
+ min: 0,
+ editType: 'none',
+ description: 'Sets padding (in px) between edge of label and edge of new shape.'
+ },
+ editType: 'none'
+ },
editType: 'none'
},
diff --git a/src/components/shapes/draw_newshape/defaults.js b/src/components/shapes/draw_newshape/defaults.js
index 3043559025d..059688c9986 100644
--- a/src/components/shapes/draw_newshape/defaults.js
+++ b/src/components/shapes/draw_newshape/defaults.js
@@ -1,8 +1,18 @@
'use strict';
var Color = require('../../color');
+var Lib = require('../../../lib');
+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';
+}
+
module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce) {
coerce('newshape.drawdirection');
coerce('newshape.layer');
@@ -16,6 +26,17 @@ module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce
coerce('newshape.line.dash');
}
+ var isLine = layoutIn.dragmode === 'drawline';
+ var labelText = coerce('newshape.label.text');
+ if(labelText) {
+ coerce('newshape.label.textangle');
+ var labelTextPosition = coerce('newshape.label.textposition', isLine ? 'middle' : 'middle center');
+ coerce('newshape.label.xanchor');
+ coerce('newshape.label.yanchor', dfltLabelYanchor(isLine, labelTextPosition));
+ coerce('newshape.label.padding');
+ Lib.coerceFont(coerce, 'newshape.label.font', layoutOut.font);
+ }
+
coerce('activeshape.fillcolor');
coerce('activeshape.opacity');
};
diff --git a/src/components/shapes/draw_newshape/newshapes.js b/src/components/shapes/draw_newshape/newshapes.js
index 0b2494477da..582b307c603 100644
--- a/src/components/shapes/draw_newshape/newshapes.js
+++ b/src/components/shapes/draw_newshape/newshapes.js
@@ -77,6 +77,8 @@ module.exports = function newShapes(outlines, dragOptions) {
var newShape = {
editable: true,
+ label: newStyle.label,
+
xref: xPaper ? 'paper' : xaxis._id,
yref: yPaper ? 'paper' : yaxis._id,
diff --git a/test/image/baselines/zz-text_on_shapes_basic.png b/test/image/baselines/zz-text_on_shapes_basic.png
new file mode 100644
index 00000000000..72f5fbfe570
Binary files /dev/null and b/test/image/baselines/zz-text_on_shapes_basic.png differ
diff --git a/test/image/baselines/zz-text_on_shapes_position.png b/test/image/baselines/zz-text_on_shapes_position.png
new file mode 100644
index 00000000000..d0e81973cfc
Binary files /dev/null and b/test/image/baselines/zz-text_on_shapes_position.png differ
diff --git a/test/image/baselines/zz-text_on_shapes_reversed_axes.png b/test/image/baselines/zz-text_on_shapes_reversed_axes.png
new file mode 100644
index 00000000000..97a8c237113
Binary files /dev/null and b/test/image/baselines/zz-text_on_shapes_reversed_axes.png differ
diff --git a/test/image/mocks/zz-text_on_shapes_basic.json b/test/image/mocks/zz-text_on_shapes_basic.json
new file mode 100644
index 00000000000..0918a6a94bf
--- /dev/null
+++ b/test/image/mocks/zz-text_on_shapes_basic.json
@@ -0,0 +1,28 @@
+{
+ "data":[{
+ "x":[0,10],
+ "y":[0,10],
+ "mode":"markers"
+ }],
+ "layout": {
+ "xaxis":{"title":{"text":"linear"},"range":[0,10],"type":"linear","showgrid":false,"zeroline":false,"showticklabels":false},
+ "yaxis":{"title":{"text":"linear"},"range":[0,10],"type":"linear","showgrid":false,"zeroline":false,"showticklabels":false},
+ "height":400,
+ "width":800,
+ "margin": {"l":20,"r":20,"pad":0},
+ "showlegend":false,
+ "shapes":[
+ {"label":{"text":"angle zero", "textangle":0, "textposition": "end", "padding": 20},"xref":"paper","yref":"paper","x0":0.6,"x1":0.8,"y0":0.3,"y1":0.6, "type": "line"},
+ {"label":{"text":"top right, xanchor right,
angle 45", "xanchor":"right", "textangle":45, "textposition": "top right", "padding": 20}, "layer":"below","xref":"paper","yref":"paper","x0":0.5,"x1":0.6,"y0":0.4,"y1":0.6},
+ {"label":{"text":"top right", "textposition": "top right"}, "xref":"paper","yref":"paper","type":"circle","x0":0.3,"x1":0.35,"y0":0.2,"y1":0.4},
+ {"label":{"text":"xanchor right, position not set
two lines", "xanchor":"right", "padding": 10, "font": {"family":"Courier New, monospace", "size":20}}, "xref":"paper","yref":"paper","type":"line","x0":0.8,"x1":0.9,"y0":0.8,"y1":0.9},
+ {"label":{"text":"position auto, xanchor center, angle -30", "xanchor":"center", "textangle":-30}, "layer":"below","x0":2,"x1":3,"y0":7,"y1":9.5,"opacity":0.5,"fillcolor":"#05e","line":{"width":3,"color":"#025","dash":"dashdot"}},
+ {"label":{"text":"position auto, yanchor top", "yanchor": "top"}, "yref":"paper","type":"line","x0":1.1,"x1":2.4,"y0":0.1,"y1":0.4,"line":{"color":"#039","dash":"dot","width":2}},
+ {"label":{"text":"position auto, xanchor center", "xanchor":"center"}, "xref":"paper","x0":0.8,"x1":0.9,"y0":1,"y1":3,"fillcolor":"#ccc"},
+ {"label":{"text":"position end", "textposition": "end"}, "yref":"paper","type":"line","x0":4.1,"x1":6.4,"y0":0.2,"y1":0.0,"line":{"color":"#339","width":3}}
+ ]
+ },
+ "config": {
+ "editable": true
+ }
+}
diff --git a/test/image/mocks/zz-text_on_shapes_position.json b/test/image/mocks/zz-text_on_shapes_position.json
new file mode 100644
index 00000000000..3969f334971
--- /dev/null
+++ b/test/image/mocks/zz-text_on_shapes_position.json
@@ -0,0 +1,66 @@
+{
+ "data":[{
+ "x":[0,10],
+ "y":[0,10],
+ "mode":"markers"
+ }],
+ "layout": {
+ "xaxis":{"title":{"text":"linear"},"range":[0,100],"type":"linear","showgrid":false,"zeroline":false,"showticklabels":false},
+ "yaxis":{"title":{"text":"linear"},"range":[0,100],"type":"linear","showgrid":false,"zeroline":false,"showticklabels":false},
+ "height":800,
+ "width":1200,
+ "margin": {"l":20,"r":20,"pad":0},
+ "showlegend":false,
+ "shapes":[
+ {"label":{"text":"top left", "textposition": "top left", "font": { "size": 16 }, "padding": 5}, "x0":15,"x1":30,"y0":87,"y1":98,"fillcolor":"#ccc"},
+ {"label":{"text":"top center", "textposition": "top center", "font": { "size": 16 }, "padding": 5}, "x0":43,"x1":58,"y0":87,"y1":98,"fillcolor":"#ccc"},
+ {"label":{"text":"top right", "textposition": "top right", "font": { "size": 16 }, "padding": 5}, "x0":70,"x1":85,"y0":87,"y1":98,"fillcolor":"#ccc"},
+
+ {"label":{"text":"middle left", "textposition": "middle left", "font": { "size": 16 }, "padding": 5}, "x0":15,"x1":30,"y0":69,"y1":80,"fillcolor":"#ccc"},
+ {"label":{"text":"middle center", "textposition": "middle center", "font": { "size": 16 }, "padding": 5}, "x0":43,"x1":58,"y0":69,"y1":80,"fillcolor":"#ccc"},
+ {"label":{"text":"middle right", "textposition": "middle right", "font": { "size": 16 }, "padding": 5}, "x0":70,"x1":85,"y0":69,"y1":80,"fillcolor":"#ccc"},
+
+ {"label":{"text":"bottom left", "textposition": "bottom left", "font": { "size": 16 }, "padding": 5}, "x0":15,"x1":30,"y0":52,"y1":63,"fillcolor":"#ccc"},
+ {"label":{"text":"bottom center", "textposition": "bottom center", "font": { "size": 16 }, "padding": 5}, "x0":43,"x1":58,"y0":52,"y1":63,"fillcolor":"#ccc"},
+ {"label":{"text":"bottom right", "textposition": "bottom right", "font": { "size": 16 }, "padding": 5}, "x0":70,"x1":85,"y0":52,"y1":63,"fillcolor":"#ccc"},
+
+
+ {"label":{"text":"top
start +", "textposition": "start", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":15,"x1":20,"y0":40,"y1":43,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"top
middle +", "textposition": "middle", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":43,"x1":48,"y0":40,"y1":43,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"top
end +", "textposition": "end", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":70,"x1":75,"y0":40,"y1":43,"fillcolor":"#ccc", "type": "line"},
+
+ {"label":{"text":"top
start -", "textposition": "start", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":30,"x1":35,"y0":43,"y1":40,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"top
middle -", "textposition": "middle", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":58,"x1":63,"y0":43,"y1":40,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"top
end -", "textposition": "end", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":85,"x1":90,"y0":43,"y1":40,"fillcolor":"#ccc", "type": "line"},
+
+ {"label":{"text":"top
start + R", "textposition": "start", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":15,"x0":20,"y1":30,"y0":33,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"top
middle + R", "textposition": "middle", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":43,"x0":48,"y1":30,"y0":33,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"top
end + R", "textposition": "end", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":70,"x0":75,"y1":30,"y0":33,"fillcolor":"#ccc", "type": "line"},
+
+ {"label":{"text":"top
start - R", "textposition": "start", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":30,"x0":35,"y1":33,"y0":30,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"top
middle - R", "textposition": "middle", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":58,"x0":63,"y1":33,"y0":30,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"top
end - R", "textposition": "end", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":85,"x0":90,"y1":33,"y0":30,"fillcolor":"#ccc", "type": "line"},
+
+ {"label":{"text":"bottom
start +", "yanchor": "top", "textposition": "start", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":15,"x1":20,"y0":20,"y1":23,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"bottom
middle +", "yanchor": "top", "textposition": "middle", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":43,"x1":48,"y0":20,"y1":23,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"bottom
end +", "yanchor": "top", "textposition": "end", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":70,"x1":75,"y0":20,"y1":23,"fillcolor":"#ccc", "type": "line"},
+
+ {"label":{"text":"bottom
start -", "yanchor": "top", "textposition": "start", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":30,"x1":35,"y0":23,"y1":20,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"bottom
middle -", "yanchor": "top", "textposition": "middle", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":58,"x1":63,"y0":23,"y1":20,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"bottom
end -", "yanchor": "top", "textposition": "end", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x0":85,"x1":90,"y0":23,"y1":20,"fillcolor":"#ccc", "type": "line"},
+
+ {"label":{"text":"bottom
start + R", "yanchor": "top", "textposition": "start", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":15,"x0":20,"y1":10,"y0":13,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"bottom
middle + R", "yanchor": "top", "textposition": "middle", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":43,"x0":48,"y1":10,"y0":13,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"bottom
end + R", "yanchor": "top", "textposition": "end", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":70,"x0":75,"y1":10,"y0":13,"fillcolor":"#ccc", "type": "line"},
+
+ {"label":{"text":"bottom
start - R", "yanchor": "top", "textposition": "start", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":30,"x0":35,"y1":13,"y0":10,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"bottom
middle - R", "yanchor": "top", "textposition": "middle", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":58,"x0":63,"y1":13,"y0":10,"fillcolor":"#ccc", "type": "line"},
+ {"label":{"text":"bottom
end - R", "yanchor": "top", "textposition": "end", "font": { "size": 16 }, "padding": 5, "textangle": 0}, "x1":85,"x0":90,"y1":13,"y0":10,"fillcolor":"#ccc", "type": "line"}
+
+ ]
+ },
+ "config": {
+ "editable": true,
+ "modeBarButtonsToAdd": ["drawline"]
+ }
+}
diff --git a/test/image/mocks/zz-text_on_shapes_reversed_axes.json b/test/image/mocks/zz-text_on_shapes_reversed_axes.json
new file mode 100644
index 00000000000..aeeec4a6a51
--- /dev/null
+++ b/test/image/mocks/zz-text_on_shapes_reversed_axes.json
@@ -0,0 +1,141 @@
+{
+ "data": [
+ {
+ "x": [
+ 0,
+ 50
+ ],
+ "y": [
+ 0,
+ 50
+ ]
+ }
+ ],
+ "layout": {
+ "width": 800,
+ "height": 600,
+ "margin": {
+ "t": 100,
+ "b": 50,
+ "l": 100,
+ "r": 50
+ },
+ "yaxis": {
+ "autorange": "reversed"
+ },
+ "template": {
+ "layout": {
+ "shapes": [
+ {
+ "label": {"text": "label"},
+ "name": "myPath",
+ "editable": true,
+ "layer": "below",
+ "line": {
+ "width": 0
+ },
+ "fillcolor": "gray",
+ "opacity": 0.5,
+ "xref": "paper",
+ "yref": "paper",
+ "path": "M0.5,0.3C0.5,0.9 0.9,0.9 0.9,0.3C0.9,0.1 0.5,0.1 0.5,0.3ZM0.6,0.4C0.6,0.5 0.66,0.5 0.66,0.4ZM0.74,0.4C0.74,0.5 0.8,0.5 0.8,0.4ZM0.6,0.3C0.63,0.2 0.77,0.2 0.8,0.3Z"
+ }
+ ]
+ }
+ },
+ "shapes": [
+ {
+ "label": {"text": "label 1 (top right)", "textposition": "top right"},
+ "editable": true,
+ "layer": "below",
+ "type": "rect",
+ "line": {
+ "width": 5
+ },
+ "fillcolor": "red",
+ "opacity": 0.5,
+ "x0": 25,
+ "y0": 25,
+ "x1": 75,
+ "y1": 75
+ },
+ {
+ "label": {"text": "label 2"},
+ "editable": true,
+ "type": "circle",
+ "line": {
+ "width": 5
+ },
+ "fillcolor": "green",
+ "opacity": 0.5,
+ "x0": 125,
+ "y0": 25,
+ "x1": 175,
+ "y1": 75
+ },
+ {
+ "label": {"text": "label 3"},
+ "editable": true,
+ "line": {
+ "width": 5
+ },
+ "fillcolor": "blue",
+ "path": "M250,25L225,75L275,75Z"
+ },
+ {
+ "label": {"text": "label 4"},
+ "editable": true,
+ "line": {
+ "width": 15
+ },
+ "path": "M250,225L225,275L275,275"
+ },
+ {
+ "label": {"text": "label 5"},
+ "editable": true,
+ "layer": "below",
+ "path": "M320,100C390,180 290,180 360,100Z",
+ "fillcolor": "rgba(0,127,127,0.5)",
+ "line": {
+ "width": 5
+ }
+ },
+ {
+ "label": {"text": "label 6"},
+ "editable": true,
+ "line": {
+ "width": 5,
+ "color": "orange"
+ },
+ "fillcolor": "rgba(127,255,127,0.5)",
+ "path": "M0,100V200H50L0,300Q100,300 100,200T150,200C100,300 200,300 200,200S150,200 150,100Z"
+ },
+ {
+ "label": {"text": "label 7"},
+ "editable": true,
+ "line": {
+ "width": 2
+ },
+ "fillcolor": "yellow",
+ "path": "M300,70C300,10 380,10 380,70C380,90 300,90 300,70ZM320,60C320,50 332,50 332,60ZM348,60C348,50 360,50 360,60ZM320,70C326,80 354,80 360,70Z"
+ }
+ ],
+ "dragmode": "drawline",
+ "newshape": {
+ "label": {
+ "text": "TEXT"
+ }
+ }
+ },
+ "config": {
+ "editable": false,
+ "modeBarButtonsToAdd": [
+ "drawline",
+ "drawopenpath",
+ "drawclosedpath",
+ "drawcircle",
+ "drawrect",
+ "eraseshape"
+ ]
+ }
+}
diff --git a/test/jasmine/tests/draw_newshape_test.js b/test/jasmine/tests/draw_newshape_test.js
index 33cb7e3d6dd..62419944a58 100644
--- a/test/jasmine/tests/draw_newshape_test.js
+++ b/test/jasmine/tests/draw_newshape_test.js
@@ -3,6 +3,7 @@ var parseSvgPath = require('parse-svg-path');
var Plotly = require('../../../lib/index');
var Lib = require('../../../src/lib');
+var d3Select = require('../../strict-d3').select;
var d3SelectAll = require('../../strict-d3').selectAll;
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
@@ -840,6 +841,7 @@ describe('Draw new shapes to layout', function() {
var newFig = Lib.extendFlat({}, fig);
newFig.layout.dragmode = 'drawline';
+ newFig.layout.newshape = { label: { text: 'Shape label' } };
return Plotly.react(gd, newFig);
})
@@ -851,6 +853,11 @@ describe('Draw new shapes to layout', function() {
expect(shapes.length).toEqual(++n);
var obj = shapes[n - 1]._input;
expect(obj.type).toEqual('line');
+ expect(
+ d3Select('.shape-group[data-index="' + (n - 1) + '"]')
+ .select('text')
+ .text()
+ ).toEqual('Shape label');
print(obj);
mockItem.testPos[n - 1]({
x0: obj.x0,
diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js
index a3be9f49226..112add2bee7 100644
--- a/test/jasmine/tests/shapes_test.js
+++ b/test/jasmine/tests/shapes_test.js
@@ -33,17 +33,17 @@ var dyToEnlargeHeight = { n: -10, s: 10, w: 0, e: 0, nw: -10, se: 10, ne: -10, s
// Helper functions
function getMoveLineDragElement(index) {
index = index || 0;
- return d3SelectAll('.shapelayer g[data-index="' + index + '"] path').node();
+ return d3SelectAll('.shapelayer g[drag-helper][data-index="' + index + '"] path').node();
}
function getResizeLineOverStartPointElement(index) {
index = index || 0;
- return d3SelectAll('.shapelayer g[data-index="' + index + '"] circle[data-line-point="start-point"]').node();
+ return d3SelectAll('.shapelayer g[drag-helper][data-index="' + index + '"] circle[data-line-point="start-point"]').node();
}
function getResizeLineOverEndPointElement(index) {
index = index || 0;
- return d3SelectAll('.shapelayer g[data-index="' + index + '"] circle[data-line-point="end-point"]').node();
+ return d3SelectAll('.shapelayer g[drag-helper][data-index="' + index + '"] circle[data-line-point="end-point"]').node();
}
describe('Test shapes defaults:', function() {
@@ -201,15 +201,15 @@ function countSubplots(gd) {
}
function countShapePathsInLowerLayer() {
- return d3SelectAll('.layer-below > .shapelayer > path').size();
+ return d3SelectAll('.layer-below > .shapelayer > .shape-group > path').size();
}
function countShapePathsInUpperLayer() {
- return d3SelectAll('.layer-above > .shapelayer > path').size();
+ return d3SelectAll('.layer-above > .shapelayer > .shape-group > path').size();
}
function countShapePathsInSubplots() {
- return d3SelectAll('.layer-subplot > .shapelayer > path').size();
+ return d3SelectAll('.layer-subplot > .shapelayer > .shape-group > path').size();
}
describe('Test shapes:', function() {
@@ -488,7 +488,7 @@ describe('shapes axis reference changes', function() {
afterEach(destroyGraphDiv);
function getShape(index) {
- var s = d3SelectAll('path[data-index="' + index + '"]');
+ var s = d3SelectAll('.shape-group[data-index="' + index + '"]');
expect(s.size()).toBe(1);
return s;
}
@@ -721,7 +721,7 @@ describe('Test shapes: a plot with shapes and an overlaid axis', function() {
});
function getFirstShapeNode() {
- return d3SelectAll('.shapelayer path').node();
+ return d3SelectAll('.shapelayer .shape-group path').node();
}
function assertShapeSize(shapeNode, w, h) {
@@ -1479,7 +1479,7 @@ describe('Test shapes', function() {
}
function getShapeNode(index) {
- return d3SelectAll('.shapelayer path').filter(function() {
+ return d3SelectAll('.shapelayer .shape-group path').filter(function() {
return +this.getAttribute('data-index') === index;
}).node();
}
diff --git a/test/plot-schema.json b/test/plot-schema.json
index c9d0ccad116..3d99e78433b 100644
--- a/test/plot-schema.json
+++ b/test/plot-schema.json
@@ -3579,6 +3579,91 @@
"nonzero"
]
},
+ "label": {
+ "editType": "none",
+ "font": {
+ "color": {
+ "editType": "none",
+ "valType": "color"
+ },
+ "description": "Sets the new shape label text font.",
+ "editType": "none",
+ "family": {
+ "description": "HTML font family - the typeface that will be applied by the web browser. The web browser will only be able to apply a font if it is available on the system which it operates. Provide multiple font families, separated by commas, to indicate the preference in which to apply fonts if they aren't available on the system. The Chart Studio Cloud (at https://chart-studio.plotly.com or on-premise) generates images on a server, where only a select number of fonts are installed and supported. These include *Arial*, *Balto*, *Courier New*, *Droid Sans*,, *Droid Serif*, *Droid Sans Mono*, *Gravitas One*, *Old Standard TT*, *Open Sans*, *Overpass*, *PT Sans Narrow*, *Raleway*, *Times New Roman*.",
+ "editType": "none",
+ "noBlank": true,
+ "strict": true,
+ "valType": "string"
+ },
+ "role": "object",
+ "size": {
+ "editType": "none",
+ "min": 1,
+ "valType": "number"
+ }
+ },
+ "padding": {
+ "description": "Sets padding (in px) between edge of label and edge of new shape.",
+ "dflt": 3,
+ "editType": "none",
+ "min": 0,
+ "valType": "number"
+ },
+ "role": "object",
+ "text": {
+ "description": "Sets the text to display with the new shape.",
+ "dflt": "",
+ "editType": "none",
+ "valType": "string"
+ },
+ "textangle": {
+ "description": "Sets the angle at which the label text is drawn with respect to the horizontal. For lines, angle *auto* is the same angle as the line. For all other shapes, angle *auto* is horizontal.",
+ "dflt": "auto",
+ "editType": "none",
+ "valType": "angle"
+ },
+ "textposition": {
+ "description": "Sets the position of the label text relative to the new shape. Supported values for rectangles, circles and paths are *top left*, *top center*, *top right*, *middle left*, *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.",
+ "editType": "none",
+ "valType": "enumerated",
+ "values": [
+ "top left",
+ "top center",
+ "top right",
+ "middle left",
+ "middle center",
+ "middle right",
+ "bottom left",
+ "bottom center",
+ "bottom right",
+ "start",
+ "middle",
+ "end"
+ ]
+ },
+ "xanchor": {
+ "description": "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 new shape.",
+ "dflt": "auto",
+ "editType": "none",
+ "valType": "enumerated",
+ "values": [
+ "auto",
+ "left",
+ "center",
+ "right"
+ ]
+ },
+ "yanchor": {
+ "description": "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 new shape.",
+ "editType": "none",
+ "valType": "enumerated",
+ "values": [
+ "top",
+ "middle",
+ "bottom"
+ ]
+ }
+ },
"layer": {
"description": "Specifies whether new shapes are drawn below or above traces.",
"dflt": "above",
@@ -7145,6 +7230,91 @@
"nonzero"
]
},
+ "label": {
+ "editType": "arraydraw",
+ "font": {
+ "color": {
+ "editType": "arraydraw",
+ "valType": "color"
+ },
+ "description": "Sets the shape label text font.",
+ "editType": "calc+arraydraw",
+ "family": {
+ "description": "HTML font family - the typeface that will be applied by the web browser. The web browser will only be able to apply a font if it is available on the system which it operates. Provide multiple font families, separated by commas, to indicate the preference in which to apply fonts if they aren't available on the system. The Chart Studio Cloud (at https://chart-studio.plotly.com or on-premise) generates images on a server, where only a select number of fonts are installed and supported. These include *Arial*, *Balto*, *Courier New*, *Droid Sans*,, *Droid Serif*, *Droid Sans Mono*, *Gravitas One*, *Old Standard TT*, *Open Sans*, *Overpass*, *PT Sans Narrow*, *Raleway*, *Times New Roman*.",
+ "editType": "calc+arraydraw",
+ "noBlank": true,
+ "strict": true,
+ "valType": "string"
+ },
+ "role": "object",
+ "size": {
+ "editType": "calc+arraydraw",
+ "min": 1,
+ "valType": "number"
+ }
+ },
+ "padding": {
+ "description": "Sets padding (in px) between edge of label and edge of shape.",
+ "dflt": 3,
+ "editType": "arraydraw",
+ "min": 0,
+ "valType": "number"
+ },
+ "role": "object",
+ "text": {
+ "description": "Sets the text to display with shape.",
+ "dflt": "",
+ "editType": "arraydraw",
+ "valType": "string"
+ },
+ "textangle": {
+ "description": "Sets the angle at which the label text is drawn with respect to the horizontal. For lines, angle *auto* is the same angle as the line. For all other shapes, angle *auto* is horizontal.",
+ "dflt": "auto",
+ "editType": "calc+arraydraw",
+ "valType": "angle"
+ },
+ "textposition": {
+ "description": "Sets the position of the label text relative to the shape. Supported values for rectangles, circles and paths are *top left*, *top center*, *top right*, *middle left*, *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.",
+ "editType": "arraydraw",
+ "valType": "enumerated",
+ "values": [
+ "top left",
+ "top center",
+ "top right",
+ "middle left",
+ "middle center",
+ "middle right",
+ "bottom left",
+ "bottom center",
+ "bottom right",
+ "start",
+ "middle",
+ "end"
+ ]
+ },
+ "xanchor": {
+ "description": "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.",
+ "dflt": "auto",
+ "editType": "calc+arraydraw",
+ "valType": "enumerated",
+ "values": [
+ "auto",
+ "left",
+ "center",
+ "right"
+ ]
+ },
+ "yanchor": {
+ "description": "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.",
+ "editType": "calc+arraydraw",
+ "valType": "enumerated",
+ "values": [
+ "top",
+ "middle",
+ "bottom"
+ ]
+ }
+ },
"layer": {
"description": "Specifies whether shapes are drawn below or above traces.",
"dflt": "above",