Skip to content

Commit c662738

Browse files
committed
Merge pull request #487 from plotly/editable-drag-bugs
Legend: fix drag movement when `editable: true`
2 parents 23421af + ee0b452 commit c662738

File tree

4 files changed

+216
-30
lines changed

4 files changed

+216
-30
lines changed

src/components/legend/draw.js

+13-23
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ var d3 = require('d3');
1313

1414
var Plotly = require('../../plotly');
1515
var Lib = require('../../lib');
16-
var setCursor = require('../../lib/setcursor');
1716
var Plots = require('../../plots/plots');
1817
var dragElement = require('../dragelement');
1918
var Drawing = require('../drawing');
@@ -326,38 +325,29 @@ module.exports = function draw(gd) {
326325
}
327326

328327
if(gd._context.editable) {
329-
var xf,
330-
yf,
331-
x0,
332-
y0,
333-
lw,
334-
lh;
328+
var xf, yf, x0, y0;
329+
330+
legend.classed('cursor-move', true);
335331

336332
dragElement.init({
337333
element: legend.node(),
338334
prepFn: function() {
339-
x0 = Number(legend.attr('x'));
340-
y0 = Number(legend.attr('y'));
341-
lw = Number(legend.attr('width'));
342-
lh = Number(legend.attr('height'));
343-
setCursor(legend);
335+
var transform = Lib.getTranslate(legend);
336+
337+
x0 = transform.x;
338+
y0 = transform.y;
344339
},
345340
moveFn: function(dx, dy) {
346-
var gs = gd._fullLayout._size;
347-
348-
legend.call(Drawing.setPosition, x0+dx, y0+dy);
341+
var newX = x0 + dx,
342+
newY = y0 + dy;
349343

350-
xf = dragElement.align(x0+dx, lw, gs.l, gs.l+gs.w,
351-
opts.xanchor);
352-
yf = dragElement.align(y0+dy+lh, -lh, gs.t+gs.h, gs.t,
353-
opts.yanchor);
344+
var transform = 'translate(' + newX + ', ' + newY + ')';
345+
legend.attr('transform', transform);
354346

355-
var csr = dragElement.getCursor(xf, yf,
356-
opts.xanchor, opts.yanchor);
357-
setCursor(legend, csr);
347+
xf = dragElement.align(newX, 0, gs.l, gs.l+gs.w, opts.xanchor);
348+
yf = dragElement.align(newY, 0, gs.t+gs.h, gs.t, opts.yanchor);
358349
},
359350
doneFn: function(dragged) {
360-
setCursor(legend);
361351
if(dragged && xf !== undefined && yf !== undefined) {
362352
Plotly.relayout(gd, {'legend.x': xf, 'legend.y': yf});
363353
}

src/lib/index.js

+36
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,42 @@ lib.addStyleRule = function(selector, styleString) {
430430
else console.warn('addStyleRule failed');
431431
};
432432

433+
lib.getTranslate = function(element) {
434+
435+
var re = /(\btranslate\()(\d*\.?\d*)([^\d]*)(\d*\.?\d*)([^\d]*)(.*)/,
436+
getter = element.attr ? 'attr' : 'getAttribute',
437+
transform = element[getter]('transform') || '';
438+
439+
var translate = transform.replace(re, function(match, p1, p2, p3, p4) {
440+
return [p2, p4].join(' ');
441+
})
442+
.split(' ');
443+
444+
return {
445+
x: +translate[0] || 0,
446+
y: +translate[1] || 0
447+
};
448+
};
449+
450+
lib.setTranslate = function(element, x, y) {
451+
452+
var re = /(\btranslate\(.*?\);?)/,
453+
getter = element.attr ? 'attr' : 'getAttribute',
454+
setter = element.attr ? 'attr' : 'setAttribute',
455+
transform = element[getter]('transform') || '';
456+
457+
x = x || 0;
458+
y = y || 0;
459+
460+
transform = transform.replace(re, '').trim();
461+
transform += ' translate(' + x + ', ' + y + ')';
462+
transform = transform.trim();
463+
464+
element[setter]('transform', transform);
465+
466+
return transform;
467+
};
468+
433469
lib.isIE = function() {
434470
return typeof window.navigator.msSaveBlob !== 'undefined';
435471
};

test/jasmine/tests/config_test.js

+80-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
var Plotly = require('@lib/index');
22
var createGraphDiv = require('../assets/create_graph_div');
33
var destroyGraphDiv = require('../assets/destroy_graph_div');
4+
var mouseEvent = require('../assets/mouse_event');
45

56
describe('config argument', function() {
67

7-
var gd;
8+
describe('showLink attribute', function() {
89

9-
beforeEach(function(done) {
10-
gd = createGraphDiv();
11-
done();
12-
});
10+
var gd;
1311

14-
afterEach(destroyGraphDiv);
12+
beforeEach(function(done) {
13+
gd = createGraphDiv();
14+
done();
15+
});
1516

16-
describe('showLink attribute', function() {
17+
afterEach(destroyGraphDiv);
1718

1819
it('should not display the edit link by default', function() {
1920
Plotly.plot(gd, [], {});
@@ -39,4 +40,76 @@ describe('config argument', function() {
3940
expect(bBox.height).toBeGreaterThan(0);
4041
});
4142
});
43+
44+
45+
describe('editable attribute', function() {
46+
47+
var gd;
48+
49+
beforeEach(function(done) {
50+
gd = createGraphDiv();
51+
52+
Plotly.plot(gd, [
53+
{ x: [1,2,3], y: [1,2,3] },
54+
{ x: [1,2,3], y: [3,2,1] }
55+
], {
56+
width: 600,
57+
height: 400
58+
}, { editable: true })
59+
.then(done);
60+
});
61+
62+
afterEach(destroyGraphDiv);
63+
64+
function checkIfEditable(elClass, text) {
65+
var label = document.getElementsByClassName(elClass)[0];
66+
67+
expect(label.textContent).toBe(text);
68+
69+
var labelBox = label.getBoundingClientRect(),
70+
labelX = labelBox.left + labelBox.width / 2,
71+
labelY = labelBox.top + labelBox.height / 2;
72+
73+
mouseEvent('click', labelX, labelY);
74+
75+
var editBox = document.getElementsByClassName('plugin-editable editable')[0];
76+
expect(editBox).toBeDefined();
77+
expect(editBox.textContent).toBe(text);
78+
expect(editBox.getAttribute('contenteditable')).toBe('true');
79+
}
80+
81+
it('should make titles editable', function() {
82+
checkIfEditable('gtitle', 'Click to enter Plot title');
83+
});
84+
85+
it('should make x axes labels editable', function() {
86+
checkIfEditable('g-xtitle', 'Click to enter X axis title');
87+
});
88+
89+
it('should make y axes labels editable', function() {
90+
checkIfEditable('g-ytitle', 'Click to enter Y axis title');
91+
});
92+
93+
it('should make legend labels editable', function() {
94+
checkIfEditable('legendtext', 'trace 0');
95+
});
96+
97+
it('should make legends draggable', function() {
98+
99+
var legend = document.getElementsByClassName('legend')[0],
100+
legendBox = legend.getBoundingClientRect(),
101+
legendX = legendBox.left + legendBox.width / 2,
102+
legendY = legendBox.top + legendBox.height / 2;
103+
104+
mouseEvent('mousedown', legendX, legendY);
105+
mouseEvent('mousemove', legendX - 20, legendY + 20);
106+
mouseEvent('mouseup', legendX - 20, legendY + 20);
107+
108+
var movedlegendBox = legend.getBoundingClientRect();
109+
110+
expect(movedlegendBox.left).not.toBe(legendBox.left);
111+
expect(movedlegendBox.top).not.toBe(legendBox.top);
112+
113+
});
114+
});
42115
});

test/jasmine/tests/lib_test.js

+87
Original file line numberDiff line numberDiff line change
@@ -770,4 +770,91 @@ describe('Test lib.js:', function() {
770770
});
771771
});
772772

773+
describe('getTranslate', function() {
774+
775+
it('should work with regular DOM elements', function() {
776+
var el = document.createElement('div');
777+
778+
expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 });
779+
780+
el.setAttribute('transform', 'translate(123.45px, 67)');
781+
expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 67 });
782+
783+
el.setAttribute('transform', 'translate(123.45)');
784+
expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 0 });
785+
786+
el.setAttribute('transform', 'translate(1 2)');
787+
expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 });
788+
789+
el.setAttribute('transform', 'translate(1 2); rotate(20deg)');
790+
expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 });
791+
792+
el.setAttribute('transform', 'rotate(20deg)');
793+
expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 });
794+
});
795+
796+
it('should work with d3 elements', function() {
797+
var el = d3.select(document.createElement('div'));
798+
799+
el.attr('transform', 'translate(123.45px, 67)');
800+
expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 67 });
801+
802+
el.attr('transform', 'translate(123.45)');
803+
expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 0 });
804+
805+
el.attr('transform', 'translate(1 2)');
806+
expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 });
807+
808+
el.attr('transform', 'translate(1 2); rotate(20)');
809+
expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 });
810+
811+
el.attr('transform', 'rotate(20)');
812+
expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 });
813+
});
814+
815+
});
816+
817+
describe('setTranslate', function() {
818+
819+
it('should work with regular DOM elements', function() {
820+
var el = document.createElement('div');
821+
822+
Lib.setTranslate(el, 5);
823+
expect(el.getAttribute('transform')).toBe('translate(5, 0)');
824+
825+
Lib.setTranslate(el, 10, 20);
826+
expect(el.getAttribute('transform')).toBe('translate(10, 20)');
827+
828+
Lib.setTranslate(el, 30, 40);
829+
expect(el.getAttribute('transform')).toBe('translate(30, 40)');
830+
831+
Lib.setTranslate(el);
832+
expect(el.getAttribute('transform')).toBe('translate(0, 0)');
833+
834+
el.setAttribute('transform', 'translate(0, 0); rotate(30)');
835+
Lib.setTranslate(el, 30, 40);
836+
expect(el.getAttribute('transform')).toBe('rotate(30) translate(30, 40)');
837+
});
838+
839+
it('should work with d3 elements', function() {
840+
var el = d3.select(document.createElement('div'));
841+
842+
Lib.setTranslate(el, 5);
843+
expect(el.attr('transform')).toBe('translate(5, 0)');
844+
845+
Lib.setTranslate(el, 10, 20);
846+
expect(el.attr('transform')).toBe('translate(10, 20)');
847+
848+
Lib.setTranslate(el, 30, 40);
849+
expect(el.attr('transform')).toBe('translate(30, 40)');
850+
851+
Lib.setTranslate(el);
852+
expect(el.attr('transform')).toBe('translate(0, 0)');
853+
854+
el.attr('transform', 'translate(0, 0); rotate(30)');
855+
Lib.setTranslate(el, 30, 40);
856+
expect(el.attr('transform')).toBe('rotate(30) translate(30, 40)');
857+
});
858+
});
859+
773860
});

0 commit comments

Comments
 (0)