Skip to content

Commit 891b6a1

Browse files
authored
Merge pull request #4775 from plotly/shape-drawing
Draw & adjust new shapes over image trace and cartesian subplots
2 parents 6a8a8dc + 2911e4f commit 891b6a1

34 files changed

+3377
-192
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"ndarray": "^1.0.18",
103103
"ndarray-fill": "^1.0.2",
104104
"ndarray-homography": "^1.0.0",
105+
"parse-svg-path": "^0.1.2",
105106
"point-cluster": "^3.1.8",
106107
"polybooljs": "^1.2.0",
107108
"regl": "^1.3.11",

src/components/dragelement/helpers.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright 2012-2020, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
exports.selectMode = function(dragmode) {
12+
return (
13+
dragmode === 'lasso' ||
14+
dragmode === 'select'
15+
);
16+
};
17+
18+
exports.drawMode = function(dragmode) {
19+
return (
20+
dragmode === 'drawclosedpath' ||
21+
dragmode === 'drawopenpath' ||
22+
dragmode === 'drawline' ||
23+
dragmode === 'drawrect' ||
24+
dragmode === 'drawcircle'
25+
);
26+
};
27+
28+
exports.openMode = function(dragmode) {
29+
return (
30+
dragmode === 'drawline' ||
31+
dragmode === 'drawopenpath'
32+
);
33+
};
34+
35+
exports.rectMode = function(dragmode) {
36+
return (
37+
dragmode === 'select' ||
38+
dragmode === 'drawline' ||
39+
dragmode === 'drawrect' ||
40+
dragmode === 'drawcircle'
41+
);
42+
};
43+
44+
exports.freeMode = function(dragmode) {
45+
return (
46+
dragmode === 'lasso' ||
47+
dragmode === 'drawclosedpath' ||
48+
dragmode === 'drawopenpath'
49+
);
50+
};
51+
52+
exports.selectingOrDrawing = function(dragmode) {
53+
return (
54+
exports.freeMode(dragmode) ||
55+
exports.rectMode(dragmode)
56+
);
57+
};

src/components/fx/layout_attributes.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,20 @@ module.exports = {
4444
dragmode: {
4545
valType: 'enumerated',
4646
role: 'info',
47-
values: ['zoom', 'pan', 'select', 'lasso', 'orbit', 'turntable', false],
47+
values: [
48+
'zoom',
49+
'pan',
50+
'select',
51+
'lasso',
52+
'drawclosedpath',
53+
'drawopenpath',
54+
'drawline',
55+
'drawrect',
56+
'drawcircle',
57+
'orbit',
58+
'turntable',
59+
false
60+
],
4861
dflt: 'zoom',
4962
editType: 'modebar',
5063
description: [
@@ -161,9 +174,9 @@ module.exports = {
161174
values: ['h', 'v', 'd', 'any'],
162175
dflt: 'any',
163176
description: [
164-
'When "dragmode" is set to "select", this limits the selection of the drag to',
165-
'horizontal, vertical or diagonal. "h" only allows horizontal selection,',
166-
'"v" only vertical, "d" only diagonal and "any" sets no limit.'
177+
'When `dragmode` is set to *select*, this limits the selection of the drag to',
178+
'horizontal, vertical or diagonal. *h* only allows horizontal selection,',
179+
'*v* only vertical, *d* only diagonal and *any* sets no limit.'
167180
].join(' '),
168181
editType: 'none'
169182
}

src/components/modebar/buttons.js

+54-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
var Registry = require('../../registry');
1212
var Plots = require('../../plots/plots');
1313
var axisIds = require('../../plots/cartesian/axis_ids');
14-
var Lib = require('../../lib');
1514
var Icons = require('../../fonts/ploticon');
16-
15+
var eraseActiveShape = require('../shapes/draw').eraseActiveShape;
16+
var Lib = require('../../lib');
1717
var _ = Lib._;
1818

1919
var modeBarButtons = module.exports = {};
@@ -134,6 +134,58 @@ modeBarButtons.lasso2d = {
134134
click: handleCartesian
135135
};
136136

137+
modeBarButtons.drawclosedpath = {
138+
name: 'drawclosedpath',
139+
title: function(gd) { return _(gd, 'Draw closed freeform'); },
140+
attr: 'dragmode',
141+
val: 'drawclosedpath',
142+
icon: Icons.drawclosedpath,
143+
click: handleCartesian
144+
};
145+
146+
modeBarButtons.drawopenpath = {
147+
name: 'drawopenpath',
148+
title: function(gd) { return _(gd, 'Draw open freeform'); },
149+
attr: 'dragmode',
150+
val: 'drawopenpath',
151+
icon: Icons.drawopenpath,
152+
click: handleCartesian
153+
};
154+
155+
modeBarButtons.drawline = {
156+
name: 'drawline',
157+
title: function(gd) { return _(gd, 'Draw line'); },
158+
attr: 'dragmode',
159+
val: 'drawline',
160+
icon: Icons.drawline,
161+
click: handleCartesian
162+
};
163+
164+
modeBarButtons.drawrect = {
165+
name: 'drawrect',
166+
title: function(gd) { return _(gd, 'Draw rectangle'); },
167+
attr: 'dragmode',
168+
val: 'drawrect',
169+
icon: Icons.drawrect,
170+
click: handleCartesian
171+
};
172+
173+
modeBarButtons.drawcircle = {
174+
name: 'drawcircle',
175+
title: function(gd) { return _(gd, 'Draw circle'); },
176+
attr: 'dragmode',
177+
val: 'drawcircle',
178+
icon: Icons.drawcircle,
179+
click: handleCartesian
180+
};
181+
182+
modeBarButtons.eraseshape = {
183+
name: 'eraseshape',
184+
title: function(gd) { return _(gd, 'Erase active shape'); },
185+
icon: Icons.eraseshape,
186+
click: eraseActiveShape
187+
};
188+
137189
modeBarButtons.zoomIn2d = {
138190
name: 'zoomIn2d',
139191
title: function(gd) { return _(gd, 'Zoom in'); },

src/components/modebar/manage.js

+28
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ module.exports = function manageModeBar(gd) {
6767
else fullLayout._modeBar = createModeBar(gd, buttonGroups);
6868
};
6969

70+
var DRAW_MODES = [
71+
'drawline',
72+
'drawopenpath',
73+
'drawclosedpath',
74+
'drawcircle',
75+
'drawrect',
76+
'eraseshape'
77+
];
78+
7079
// logic behind which buttons are displayed by default
7180
function getButtonGroups(gd) {
7281
var fullLayout = gd._fullLayout;
@@ -170,6 +179,25 @@ function getButtonGroups(gd) {
170179
dragModeGroup.push('select2d', 'lasso2d');
171180
}
172181

182+
// accept pre-defined buttons as string
183+
if(Array.isArray(buttonsToAdd)) {
184+
var newList = [];
185+
for(var i = 0; i < buttonsToAdd.length; i++) {
186+
var b = buttonsToAdd[i];
187+
if(typeof b === 'string') {
188+
if(DRAW_MODES.indexOf(b) !== -1) {
189+
if(
190+
fullLayout._has('mapbox') || // draw shapes in paper coordinate (could be improved in future to support data coordinate, when there is no pitch)
191+
fullLayout._has('cartesian') // draw shapes in data coordinate
192+
) {
193+
dragModeGroup.push(b);
194+
}
195+
}
196+
} else newList.push(b);
197+
}
198+
buttonsToAdd = newList;
199+
}
200+
173201
addGroup(dragModeGroup);
174202
addGroup(zoomGroup.concat(resetGroup));
175203
addGroup(hoverGroup);

src/components/shapes/attributes.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,31 @@ module.exports = templatedArray('shape', {
235235
role: 'info',
236236
editType: 'arraydraw',
237237
description: [
238-
'Sets the color filling the shape\'s interior.'
238+
'Sets the color filling the shape\'s interior. Only applies to closed shapes.'
239239
].join(' ')
240240
},
241+
fillrule: {
242+
valType: 'enumerated',
243+
values: ['evenodd', 'nonzero'],
244+
dflt: 'evenodd',
245+
role: 'info',
246+
editType: 'arraydraw',
247+
description: [
248+
'Determines which regions of complex paths constitute the interior.',
249+
'For more info please visit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule'
250+
].join(' ')
251+
},
252+
editable: {
253+
valType: 'boolean',
254+
role: 'info',
255+
dflt: false,
256+
editType: 'calc+arraydraw',
257+
description: [
258+
'Determines whether the shape could be activated for edit or not.',
259+
'Has no effect when the older editable shapes mode is enabled via',
260+
'`config.editable` or `config.edits.shapePosition`.'
261+
].join(' ')
262+
},
263+
241264
editType: 'arraydraw'
242265
});

src/components/shapes/defaults.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,24 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
3030
}
3131

3232
var visible = coerce('visible');
33-
3433
if(!visible) return;
3534

35+
var path = coerce('path');
36+
var dfltType = path ? 'path' : 'rect';
37+
var shapeType = coerce('type', dfltType);
38+
if(shapeOut.type !== 'path') delete shapeOut.path;
39+
40+
coerce('editable');
3641
coerce('layer');
3742
coerce('opacity');
3843
coerce('fillcolor');
39-
coerce('line.color');
40-
coerce('line.width');
41-
coerce('line.dash');
44+
coerce('fillrule');
45+
var lineWidth = coerce('line.width');
46+
if(lineWidth) {
47+
coerce('line.color');
48+
coerce('line.dash');
49+
}
4250

43-
var dfltType = shapeIn.path ? 'path' : 'rect';
44-
var shapeType = coerce('type', dfltType);
4551
var xSizeMode = coerce('xsizemode');
4652
var ySizeMode = coerce('ysizemode');
4753

0 commit comments

Comments
 (0)