diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index f39935acf26..26641cff7bd 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -13,7 +13,6 @@ var d3 = require('d3'); var Plotly = require('../../plotly'); var Lib = require('../../lib'); -var setCursor = require('../../lib/setcursor'); var Plots = require('../../plots/plots'); var dragElement = require('../dragelement'); var Drawing = require('../drawing'); @@ -326,38 +325,29 @@ module.exports = function draw(gd) { } if(gd._context.editable) { - var xf, - yf, - x0, - y0, - lw, - lh; + var xf, yf, x0, y0; + + legend.classed('cursor-move', true); dragElement.init({ element: legend.node(), prepFn: function() { - x0 = Number(legend.attr('x')); - y0 = Number(legend.attr('y')); - lw = Number(legend.attr('width')); - lh = Number(legend.attr('height')); - setCursor(legend); + var transform = Lib.getTranslate(legend); + + x0 = transform.x; + y0 = transform.y; }, moveFn: function(dx, dy) { - var gs = gd._fullLayout._size; - - legend.call(Drawing.setPosition, x0+dx, y0+dy); + var newX = x0 + dx, + newY = y0 + dy; - xf = dragElement.align(x0+dx, lw, gs.l, gs.l+gs.w, - opts.xanchor); - yf = dragElement.align(y0+dy+lh, -lh, gs.t+gs.h, gs.t, - opts.yanchor); + var transform = 'translate(' + newX + ', ' + newY + ')'; + legend.attr('transform', transform); - var csr = dragElement.getCursor(xf, yf, - opts.xanchor, opts.yanchor); - setCursor(legend, csr); + xf = dragElement.align(newX, 0, gs.l, gs.l+gs.w, opts.xanchor); + yf = dragElement.align(newY, 0, gs.t+gs.h, gs.t, opts.yanchor); }, doneFn: function(dragged) { - setCursor(legend); if(dragged && xf !== undefined && yf !== undefined) { Plotly.relayout(gd, {'legend.x': xf, 'legend.y': yf}); } diff --git a/src/lib/index.js b/src/lib/index.js index 8f2235928cd..0a6b8f839bc 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -430,6 +430,42 @@ lib.addStyleRule = function(selector, styleString) { else console.warn('addStyleRule failed'); }; +lib.getTranslate = function(element) { + + var re = /(\btranslate\()(\d*\.?\d*)([^\d]*)(\d*\.?\d*)([^\d]*)(.*)/, + getter = element.attr ? 'attr' : 'getAttribute', + transform = element[getter]('transform') || ''; + + var translate = transform.replace(re, function(match, p1, p2, p3, p4) { + return [p2, p4].join(' '); + }) + .split(' '); + + return { + x: +translate[0] || 0, + y: +translate[1] || 0 + }; +}; + +lib.setTranslate = function(element, x, y) { + + var re = /(\btranslate\(.*?\);?)/, + getter = element.attr ? 'attr' : 'getAttribute', + setter = element.attr ? 'attr' : 'setAttribute', + transform = element[getter]('transform') || ''; + + x = x || 0; + y = y || 0; + + transform = transform.replace(re, '').trim(); + transform += ' translate(' + x + ', ' + y + ')'; + transform = transform.trim(); + + element[setter]('transform', transform); + + return transform; +}; + lib.isIE = function() { return typeof window.navigator.msSaveBlob !== 'undefined'; }; diff --git a/test/jasmine/tests/config_test.js b/test/jasmine/tests/config_test.js index b2d3e9d8d1b..a72fcb28e71 100644 --- a/test/jasmine/tests/config_test.js +++ b/test/jasmine/tests/config_test.js @@ -1,19 +1,20 @@ var Plotly = require('@lib/index'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var mouseEvent = require('../assets/mouse_event'); describe('config argument', function() { - var gd; + describe('showLink attribute', function() { - beforeEach(function(done) { - gd = createGraphDiv(); - done(); - }); + var gd; - afterEach(destroyGraphDiv); + beforeEach(function(done) { + gd = createGraphDiv(); + done(); + }); - describe('showLink attribute', function() { + afterEach(destroyGraphDiv); it('should not display the edit link by default', function() { Plotly.plot(gd, [], {}); @@ -39,4 +40,76 @@ describe('config argument', function() { expect(bBox.height).toBeGreaterThan(0); }); }); + + + describe('editable attribute', function() { + + var gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, [ + { x: [1,2,3], y: [1,2,3] }, + { x: [1,2,3], y: [3,2,1] } + ], { + width: 600, + height: 400 + }, { editable: true }) + .then(done); + }); + + afterEach(destroyGraphDiv); + + function checkIfEditable(elClass, text) { + var label = document.getElementsByClassName(elClass)[0]; + + expect(label.textContent).toBe(text); + + var labelBox = label.getBoundingClientRect(), + labelX = labelBox.left + labelBox.width / 2, + labelY = labelBox.top + labelBox.height / 2; + + mouseEvent('click', labelX, labelY); + + var editBox = document.getElementsByClassName('plugin-editable editable')[0]; + expect(editBox).toBeDefined(); + expect(editBox.textContent).toBe(text); + expect(editBox.getAttribute('contenteditable')).toBe('true'); + } + + it('should make titles editable', function() { + checkIfEditable('gtitle', 'Click to enter Plot title'); + }); + + it('should make x axes labels editable', function() { + checkIfEditable('g-xtitle', 'Click to enter X axis title'); + }); + + it('should make y axes labels editable', function() { + checkIfEditable('g-ytitle', 'Click to enter Y axis title'); + }); + + it('should make legend labels editable', function() { + checkIfEditable('legendtext', 'trace 0'); + }); + + it('should make legends draggable', function() { + + var legend = document.getElementsByClassName('legend')[0], + legendBox = legend.getBoundingClientRect(), + legendX = legendBox.left + legendBox.width / 2, + legendY = legendBox.top + legendBox.height / 2; + + mouseEvent('mousedown', legendX, legendY); + mouseEvent('mousemove', legendX - 20, legendY + 20); + mouseEvent('mouseup', legendX - 20, legendY + 20); + + var movedlegendBox = legend.getBoundingClientRect(); + + expect(movedlegendBox.left).not.toBe(legendBox.left); + expect(movedlegendBox.top).not.toBe(legendBox.top); + + }); + }); }); diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 3b11c1868d9..110cc948b91 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -770,4 +770,91 @@ describe('Test lib.js:', function() { }); }); + describe('getTranslate', function() { + + it('should work with regular DOM elements', function() { + var el = document.createElement('div'); + + expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 }); + + el.setAttribute('transform', 'translate(123.45px, 67)'); + expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 67 }); + + el.setAttribute('transform', 'translate(123.45)'); + expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 0 }); + + el.setAttribute('transform', 'translate(1 2)'); + expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.setAttribute('transform', 'translate(1 2); rotate(20deg)'); + expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.setAttribute('transform', 'rotate(20deg)'); + expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 }); + }); + + it('should work with d3 elements', function() { + var el = d3.select(document.createElement('div')); + + el.attr('transform', 'translate(123.45px, 67)'); + expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 67 }); + + el.attr('transform', 'translate(123.45)'); + expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 0 }); + + el.attr('transform', 'translate(1 2)'); + expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.attr('transform', 'translate(1 2); rotate(20)'); + expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.attr('transform', 'rotate(20)'); + expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 }); + }); + + }); + + describe('setTranslate', function() { + + it('should work with regular DOM elements', function() { + var el = document.createElement('div'); + + Lib.setTranslate(el, 5); + expect(el.getAttribute('transform')).toBe('translate(5, 0)'); + + Lib.setTranslate(el, 10, 20); + expect(el.getAttribute('transform')).toBe('translate(10, 20)'); + + Lib.setTranslate(el, 30, 40); + expect(el.getAttribute('transform')).toBe('translate(30, 40)'); + + Lib.setTranslate(el); + expect(el.getAttribute('transform')).toBe('translate(0, 0)'); + + el.setAttribute('transform', 'translate(0, 0); rotate(30)'); + Lib.setTranslate(el, 30, 40); + expect(el.getAttribute('transform')).toBe('rotate(30) translate(30, 40)'); + }); + + it('should work with d3 elements', function() { + var el = d3.select(document.createElement('div')); + + Lib.setTranslate(el, 5); + expect(el.attr('transform')).toBe('translate(5, 0)'); + + Lib.setTranslate(el, 10, 20); + expect(el.attr('transform')).toBe('translate(10, 20)'); + + Lib.setTranslate(el, 30, 40); + expect(el.attr('transform')).toBe('translate(30, 40)'); + + Lib.setTranslate(el); + expect(el.attr('transform')).toBe('translate(0, 0)'); + + el.attr('transform', 'translate(0, 0); rotate(30)'); + Lib.setTranslate(el, 30, 40); + expect(el.attr('transform')).toBe('rotate(30) translate(30, 40)'); + }); + }); + });