Skip to content

Commit aba25e3

Browse files
committed
Shapes: make shapes draggable
1 parent 945325f commit aba25e3

File tree

1 file changed

+145
-9
lines changed

1 file changed

+145
-9
lines changed

src/components/shapes/index.js

Lines changed: 145 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ var Axes = require('../../plots/cartesian/axes');
1717
var Color = require('../color');
1818
var Drawing = require('../drawing');
1919

20+
var dragElement = require('../dragelement');
21+
var setCursor = require('../../lib/setcursor');
2022

2123
var shapes = module.exports = {};
2224

@@ -299,15 +301,7 @@ function updateShape(gd, index, opt, value) {
299301
var options = handleShapeDefaults(optionsIn, gd._fullLayout);
300302
gd._fullLayout.shapes[index] = options;
301303

302-
var attrs = {
303-
'data-index': index,
304-
'fill-rule': 'evenodd',
305-
d: shapePath(gd, options)
306-
},
307-
clipAxes;
308-
309-
var lineColor = options.line.width ? options.line.color : 'rgba(0,0,0,0)';
310-
304+
var clipAxes;
311305
if(options.layer !== 'below') {
312306
clipAxes = (options.xref + options.yref).replace(/paper/g, '');
313307
drawShape(gd._fullLayout._shapeUpperLayer);
@@ -332,6 +326,14 @@ function updateShape(gd, index, opt, value) {
332326
}
333327

334328
function drawShape(shapeLayer) {
329+
var attrs = {
330+
'data-index': index,
331+
'fill-rule': 'evenodd',
332+
d: shapePath(gd, options)
333+
},
334+
lineColor = options.line.width ?
335+
options.line.color : 'rgba(0,0,0,0)';
336+
335337
var path = shapeLayer.append('path')
336338
.attr(attrs)
337339
.style('opacity', options.opacity)
@@ -343,6 +345,72 @@ function updateShape(gd, index, opt, value) {
343345
path.call(Drawing.setClipUrl,
344346
'clip' + gd._fullLayout._uid + clipAxes);
345347
}
348+
349+
if(!gd._context.editable) return;
350+
351+
var update;
352+
var x0, y0, x1, y1, astrX0, astrY0, astrX1, astrY1;
353+
var pathIn, astrPath;
354+
var xa, ya, x2p, y2p, p2x, p2y;
355+
dragElement.init({
356+
element: path.node(),
357+
prepFn: function() {
358+
setCursor(path, 'move');
359+
360+
xa = Axes.getFromId(gd, options.xref);
361+
ya = Axes.getFromId(gd, options.yref);
362+
363+
x2p = getDataToPixel(gd, xa);
364+
y2p = getDataToPixel(gd, ya, true);
365+
p2x = getPixelToData(gd, xa);
366+
p2y = getPixelToData(gd, ya, true);
367+
368+
var astr = 'shapes[' + index + ']';
369+
if(options.type === 'path') {
370+
pathIn = options.path;
371+
astrPath = astr + '.path';
372+
}
373+
else {
374+
x0 = x2p(options.x0);
375+
y0 = y2p(options.y0);
376+
x1 = x2p(options.x1);
377+
y1 = y2p(options.y1);
378+
379+
astrX0 = astr + '.x0';
380+
astrY0 = astr + '.y0';
381+
astrX1 = astr + '.x1';
382+
astrY1 = astr + '.y1';
383+
}
384+
385+
update = {};
386+
},
387+
moveFn: function(dx, dy) {
388+
if(options.type === 'path') {
389+
var moveX = function moveX(x) { return p2x(x2p(x) + dx); };
390+
if(xa && xa.type === 'date') moveX = encodeDate(moveX);
391+
392+
var moveY = function moveY(y) { return p2y(y2p(y) + dy); };
393+
if(ya && ya.type === 'date') moveY = encodeDate(moveY);
394+
395+
options.path = movePath(pathIn, moveX, moveY);
396+
update[astrPath] = options.path;
397+
}
398+
else {
399+
update[astrX0] = options.x0 = p2x(x0 + dx);
400+
update[astrY0] = options.y0 = p2y(y0 + dy);
401+
update[astrX1] = options.x1 = p2x(x1 + dx);
402+
update[astrY1] = options.y1 = p2y(y1 + dy);
403+
}
404+
405+
path.attr('d', shapePath(gd, options));
406+
},
407+
doneFn: function(dragged) {
408+
setCursor(path);
409+
if(dragged) {
410+
Plotly.relayout(gd, update);
411+
}
412+
}
413+
});
346414
}
347415
}
348416

@@ -375,6 +443,51 @@ function decodeDate(convertToPx) {
375443
return function(v) { return convertToPx(v.replace('_', ' ')); };
376444
}
377445

446+
function encodeDate(convertToDate) {
447+
return function(v) { return convertToDate(v).replace(' ', '_'); };
448+
}
449+
450+
function getDataToPixel(gd, axis, isVertical) {
451+
var gs = gd._fullLayout._size,
452+
dataToPixel;
453+
454+
if(axis) {
455+
var d2l = dataToLinear(axis);
456+
457+
dataToPixel = function(v) {
458+
return axis._offset + axis.l2p(d2l(v, true));
459+
};
460+
461+
if(axis.type === 'date') dataToPixel = decodeDate(dataToPixel);
462+
}
463+
else if(isVertical) {
464+
dataToPixel = function(v) { return gs.t + gs.h * (1 - v); };
465+
}
466+
else {
467+
dataToPixel = function(v) { return gs.l + gs.w * v; };
468+
}
469+
470+
return dataToPixel;
471+
}
472+
473+
function getPixelToData(gd, axis, isVertical) {
474+
var gs = gd._fullLayout._size,
475+
pixelToData;
476+
477+
if(axis) {
478+
var l2d = linearToData(axis);
479+
pixelToData = function(p) { return l2d(axis.p2l(p - axis._offset)); };
480+
}
481+
else if(isVertical) {
482+
pixelToData = function(p) { return 1 - (p - gs.t) / gs.h; };
483+
}
484+
else {
485+
pixelToData = function(p) { return (p - gs.l) / gs.w; };
486+
}
487+
488+
return pixelToData;
489+
}
490+
378491
function shapePath(gd, options) {
379492
var type = options.type,
380493
xa = Axes.getFromId(gd, options.xref),
@@ -501,6 +614,29 @@ shapes.convertPath = function(pathIn, x2p, y2p) {
501614
});
502615
};
503616

617+
function movePath(pathIn, moveX, moveY) {
618+
return pathIn.replace(segmentRE, function(segment) {
619+
var paramNumber = 0,
620+
segmentType = segment.charAt(0),
621+
xParams = paramIsX[segmentType],
622+
yParams = paramIsY[segmentType],
623+
nParams = numParams[segmentType];
624+
625+
var paramString = segment.substr(1).replace(paramRE, function(param) {
626+
if(paramNumber >= nParams) return param;
627+
628+
if(xParams[paramNumber]) param = moveX(param);
629+
else if(yParams[paramNumber]) param = moveY(param);
630+
631+
paramNumber++;
632+
633+
return param;
634+
});
635+
636+
return segmentType + paramString;
637+
});
638+
}
639+
504640
shapes.calcAutorange = function(gd) {
505641
var fullLayout = gd._fullLayout,
506642
shapeList = fullLayout.shapes,

0 commit comments

Comments
 (0)