Skip to content

Render shape label while drawing #6608

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 5 commits into from
May 19, 2023
Merged
Changes from all commits
Commits
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
16 changes: 15 additions & 1 deletion src/components/selections/select.js
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ var newShapeHelpers = require('../shapes/draw_newshape/helpers');
var handleEllipse = newShapeHelpers.handleEllipse;
var readPaths = newShapeHelpers.readPaths;

var newShapes = require('../shapes/draw_newshape/newshapes');
var newShapes = require('../shapes/draw_newshape/newshapes').newShapes;

var newSelections = require('./draw_newselection/newselections');
var activateLastSelection = require('./draw').activateLastSelection;
@@ -112,6 +112,10 @@ function prepSelect(evt, startX, startY, dragOptions, mode) {
fullLayout.newshape :
fullLayout.newselection;

if(isDrawMode) {
dragOptions.hasText = newStyle.label.text || newStyle.label.texttemplate;
}

var fillC = (isDrawMode && !isOpenMode) ? newStyle.fillcolor : 'rgba(0,0,0,0)';

var strokeC = newStyle.line.color || (
@@ -146,6 +150,16 @@ function prepSelect(evt, startX, startY, dragOptions, mode) {
.attr('transform', transform)
.attr('d', 'M0,0Z');

// create & style group for text label
if(isDrawMode && dragOptions.hasText) {
var shapeGroup = zoomLayer.select('.label-temp');
if(shapeGroup.empty()) {
shapeGroup = zoomLayer.append('g')
.classed('label-temp', true)
.classed('select-outline', true)
.style({ opacity: 0.8 });
}
}

var throttleID = fullLayout._uid + constants.SELECTID;
var selection = [];
268 changes: 268 additions & 0 deletions src/components/shapes/display_labels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
'use strict';
Copy link
Contributor Author

@emilykl emilykl May 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just the drawLabels() function which used to be in shapes/draw.js being moved into its own file (in order to call it elsewhere without circular imports). No code changes.


var Lib = require('../../lib');
var Axes = require('../../plots/cartesian/axes');
var svgTextUtils = require('../../lib/svg_text_utils');

var Drawing = require('../drawing');

var readPaths = require('./draw_newshape/helpers').readPaths;
var helpers = require('./helpers');
var getPathString = helpers.getPathString;
var shapeLabelTexttemplateVars = require('./label_texttemplate');

var FROM_TL = require('../../constants/alignment').FROM_TL;


module.exports = function drawLabel(gd, index, options, shapeGroup) {
// Remove existing label
shapeGroup.selectAll('.shape-label').remove();

// If no label text or texttemplate, return
if(!(options.label.text || options.label.texttemplate)) return;

// Text template overrides text
var text;
if(options.label.texttemplate) {
var templateValues = {};
if(options.type !== 'path') {
var _xa = Axes.getFromId(gd, options.xref);
var _ya = Axes.getFromId(gd, options.yref);
for(var key in shapeLabelTexttemplateVars) {
var val = shapeLabelTexttemplateVars[key](options, _xa, _ya);
if(val !== undefined) templateValues[key] = val;
}
}
text = Lib.texttemplateStringForShapes(options.label.texttemplate,
{},
gd._fullLayout._d3locale,
templateValues);
} else {
text = options.label.text;
}

var labelGroupAttrs = {
'data-index': index,
};
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 };
}
12 changes: 11 additions & 1 deletion src/components/shapes/display_outlines.js
Original file line number Diff line number Diff line change
@@ -24,8 +24,11 @@ var helpers = require('./draw_newshape/helpers');
var pointsOnRectangle = helpers.pointsOnRectangle;
var pointsOnEllipse = helpers.pointsOnEllipse;
var writePaths = helpers.writePaths;
var newShapes = require('./draw_newshape/newshapes');
var newShapes = require('./draw_newshape/newshapes').newShapes;
var createShapeObj = require('./draw_newshape/newshapes').createShapeObj;
var newSelections = require('../selections/draw_newselection/newselections');
var drawLabel = require('./display_labels');


module.exports = function displayOutlines(polygons, outlines, dragOptions, nCalls) {
if(!nCalls) nCalls = 0;
@@ -95,6 +98,13 @@ module.exports = function displayOutlines(polygons, outlines, dragOptions, nCall
addGroupControllers();
}

// draw label
if(isDrawMode && dragOptions.hasText) {
var shapeGroup = zoomLayer.select('.label-temp');
var shapeOptions = createShapeObj(outlines, dragOptions, dragOptions.dragmode);
drawLabel(gd, 'label-temp', shapeOptions, shapeGroup);
}

function startDragVertex(evt) {
indexI = +evt.srcElement.getAttribute('data-i');
indexJ = +evt.srcElement.getAttribute('data-j');
258 changes: 1 addition & 257 deletions src/components/shapes/draw.js
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ var Axes = require('../../plots/cartesian/axes');

var readPaths = require('./draw_newshape/helpers').readPaths;
var displayOutlines = require('./display_outlines');
var drawLabel = require('./display_labels');

var clearOutlineControllers = require('./handle_outline').clearOutlineControllers;

@@ -18,13 +19,9 @@ 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 shapeLabelTexttemplateVars = require('./label_texttemplate');
var FROM_TL = require('../../constants/alignment').FROM_TL;


// Shapes are stored in gd.layout.shapes, an array of objects
@@ -603,259 +600,6 @@ 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 text or texttemplate, return
if(!(options.label.text || options.label.texttemplate)) return;

// Text template overrides text
var text;
if(options.label.texttemplate) {
var templateValues = {};
if(options.type !== 'path') {
var _xa = Axes.getFromId(gd, options.xref);
var _ya = Axes.getFromId(gd, options.yref);
for(var key in shapeLabelTexttemplateVars) {
var val = shapeLabelTexttemplateVars[key](options, _xa, _ya);
if(val !== undefined) templateValues[key] = val;
}
}
text = Lib.texttemplateStringForShapes(options.label.texttemplate,
{},
gd._fullLayout._d3locale,
templateValues);
} else {
text = options.label.text;
}

var labelGroupAttrs = {
'data-index': index,
};
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;
112 changes: 63 additions & 49 deletions src/components/shapes/draw_newshape/newshapes.js
Original file line number Diff line number Diff line change
@@ -25,20 +25,12 @@ var writePaths = helpers.writePaths;
var ellipseOver = helpers.ellipseOver;
var fixDatesForPaths = helpers.fixDatesForPaths;

module.exports = function newShapes(outlines, dragOptions) {
function newShapes(outlines, dragOptions) {
if(!outlines.length) return;
var e = outlines[0][0]; // pick first
if(!e) return;
var d = e.getAttribute('d');

var gd = dragOptions.gd;
var newStyle = gd._fullLayout.newshape;

var plotinfo = dragOptions.plotinfo;
var xaxis = plotinfo.xaxis;
var yaxis = plotinfo.yaxis;
var xPaper = !!plotinfo.domain || !plotinfo.xaxis;
var yPaper = !!plotinfo.domain || !plotinfo.yaxis;

var isActiveShape = dragOptions.isActiveShape;
var dragmode = dragOptions.dragmode;
@@ -70,8 +62,64 @@ module.exports = function newShapes(outlines, dragOptions) {
}
}

var isOpenMode = openMode(dragmode);
var newShape = createShapeObj(outlines, dragOptions, dragmode);

clearOutline(gd);

var editHelpers = dragOptions.editHelpers;
var modifyItem = (editHelpers || {}).modifyItem;

var allShapes = [];
for(var q = 0; q < shapes.length; q++) {
var beforeEdit = gd._fullLayout.shapes[q];
allShapes[q] = beforeEdit._input;

if(
isActiveShape !== undefined &&
q === gd._fullLayout._activeShapeIndex
) {
var afterEdit = newShape;

switch(beforeEdit.type) {
case 'line':
case 'rect':
case 'circle':
modifyItem('x0', afterEdit.x0);
modifyItem('x1', afterEdit.x1);
modifyItem('y0', afterEdit.y0);
modifyItem('y1', afterEdit.y1);
break;

case 'path':
modifyItem('path', afterEdit.path);
break;
}
}
}

if(isActiveShape === undefined) {
allShapes.push(newShape); // add new shape
return allShapes;
}

return editHelpers ? editHelpers.getUpdateObj() : {};
}

function createShapeObj(outlines, dragOptions, dragmode) {
var e = outlines[0][0]; // pick first outline
var gd = dragOptions.gd;

var d = e.getAttribute('d');
var newStyle = gd._fullLayout.newshape;
var plotinfo = dragOptions.plotinfo;
var isActiveShape = dragOptions.isActiveShape;

var xaxis = plotinfo.xaxis;
var yaxis = plotinfo.yaxis;
var xPaper = !!plotinfo.domain || !plotinfo.xaxis;
var yPaper = !!plotinfo.domain || !plotinfo.yaxis;

var isOpenMode = openMode(dragmode);
var polygons = readPaths(d, gd, plotinfo, isActiveShape);

var newShape = {
@@ -191,44 +239,10 @@ module.exports = function newShapes(outlines, dragOptions) {
newShape.path = writePaths(polygons);
cell = null;
}
return newShape;
}

clearOutline(gd);

var editHelpers = dragOptions.editHelpers;
var modifyItem = (editHelpers || {}).modifyItem;

var allShapes = [];
for(var q = 0; q < shapes.length; q++) {
var beforeEdit = gd._fullLayout.shapes[q];
allShapes[q] = beforeEdit._input;

if(
isActiveShape !== undefined &&
q === gd._fullLayout._activeShapeIndex
) {
var afterEdit = newShape;

switch(beforeEdit.type) {
case 'line':
case 'rect':
case 'circle':
modifyItem('x0', afterEdit.x0);
modifyItem('x1', afterEdit.x1);
modifyItem('y0', afterEdit.y0);
modifyItem('y1', afterEdit.y1);
break;

case 'path':
modifyItem('path', afterEdit.path);
break;
}
}
}

if(isActiveShape === undefined) {
allShapes.push(newShape); // add new shape
return allShapes;
}

return editHelpers ? editHelpers.getUpdateObj() : {};
module.exports = {
newShapes: newShapes,
createShapeObj: createShapeObj,
};