diff --git a/devtools/test_dashboard/devtools.js b/devtools/test_dashboard/devtools.js index 1fcb05f0c5f..0922d66dd49 100644 --- a/devtools/test_dashboard/devtools.js +++ b/devtools/test_dashboard/devtools.js @@ -8,6 +8,8 @@ var credentials = require('../../build/credentials.json'); var Lib = require('@src/lib'); var d3 = Plotly.d3; +require('./perf'); + // Our gracious testing object var Tabs = { diff --git a/devtools/test_dashboard/perf.js b/devtools/test_dashboard/perf.js new file mode 100644 index 00000000000..99d661046c7 --- /dev/null +++ b/devtools/test_dashboard/perf.js @@ -0,0 +1,54 @@ +'use strict'; + +/* + * timeit: tool for performance testing + * f: function to be tested + * n: number of timing runs + * nchunk: optional number of repetitions per timing run - useful if + * the function is very fast. Note though that if arg is a function + * it will not be re-evaluated within the chunk, only before each chunk. + * arg: optional argument to the function. Can be a function itself + * to provide a changing input to f + */ +window.timeit = function(f, n, nchunk, arg) { + var times = new Array(n); + var totalTime = 0; + var _arg; + var t0, t1, dt; + + for(var i = 0; i < n; i++) { + if(typeof arg === 'function') _arg = arg(); + else _arg = arg; + + if(nchunk) { + t0 = performance.now(); + for(var j = 0; j < nchunk; j++) { f(_arg); } + t1 = performance.now(); + dt = (t1 - t0) / nchunk; + } + else { + t0 = performance.now(); + f(_arg); + t1 = performance.now(); + dt = t1 - t0; + } + + times[i] = dt; + totalTime += dt; + } + + var first = (times[0]).toFixed(4); + var last = (times[n - 1]).toFixed(4); + times.sort(); + var min = (times[0]).toFixed(4); + var max = (times[n - 1]).toFixed(4); + var median = (times[Math.ceil(n / 2)]).toFixed(4); + var mean = (totalTime / n).toFixed(4); + console.log((f.name || 'function') + ' timing (ms) - min: ' + min + + ' max: ' + max + + ' median: ' + median + + ' mean: ' + mean + + ' first: ' + first + + ' last: ' + last + ); +}; diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 5496471ea7d..a9cf34861d2 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -168,7 +168,8 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { fontColor: hoverFont.color }, { container: fullLayout._hoverlayer.node(), - outerContainer: fullLayout._paper.node() + outerContainer: fullLayout._paper.node(), + gd: gd }); }) .on('mouseout', function() { @@ -214,7 +215,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { }[options.align] || 'middle' }); - svgTextUtils.convertToTspans(s, drawGraphicalElements); + svgTextUtils.convertToTspans(s, gd, drawGraphicalElements); return s; } @@ -554,6 +555,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // (head/tail/text) all together dragElement.init({ element: arrowDrag.node(), + gd: gd, prepFn: function() { var pos = Drawing.getTranslate(annTextGroupInner); @@ -616,6 +618,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { // textbox and tail, leave the head untouched dragElement.init({ element: annTextGroupInner.node(), + gd: gd, prepFn: function() { baseTextTransform = annTextGroup.attr('transform'); update = {}; @@ -686,7 +689,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { } if(gd._context.editable) { - annText.call(svgTextUtils.makeEditable, annTextGroupInner) + annText.call(svgTextUtils.makeEditable, {delegate: annTextGroupInner, gd: gd}) .call(textLayout) .on('edit', function(_text) { options.text = _text; diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index 0afb0c11c54..c9ece0344cc 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -556,6 +556,7 @@ module.exports = function draw(gd, id) { dragElement.init({ element: container.node(), + gd: gd, prepFn: function() { t0 = container.attr('transform'); setCursor(container); diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index a748a37310d..5106cfaa2c9 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -26,6 +26,15 @@ dragElement.unhoverRaw = unhover.raw; /** * Abstracts click & drag interactions + * + * During the interaction, a "coverSlip" element - a transparent + * div covering the whole page - is created, which has two key effects: + * - Lets you drag beyond the boundaries of the plot itself without + * dropping (but if you drag all the way out of the browser window the + * interaction will end) + * - Freezes the cursor: whatever mouse cursor the drag element had when the + * interaction started gets copied to the coverSlip for use until mouseup + * * @param {object} options with keys: * element (required) the DOM element to drag * prepFn (optional) function(event, startX, startY) @@ -44,28 +53,20 @@ dragElement.unhoverRaw = unhover.raw; * numClicks is how many clicks we've registered within * a doubleclick time * e is the original event - * setCursor (optional) function(event) - * executed on mousemove before mousedown - * the purpose of this callback is to update the mouse cursor before - * the click & drag interaction has been initiated */ dragElement.init = function init(options) { - var gd = Lib.getPlotDiv(options.element) || {}, + var gd = options.gd, numClicks = 1, DBLCLICKDELAY = interactConstants.DBLCLICKDELAY, startX, startY, newMouseDownTime, dragCover, - initialTarget, - initialOnMouseMove; + initialTarget; if(!gd._mouseDownTime) gd._mouseDownTime = 0; function onStart(e) { - // disable call to options.setCursor(evt) - options.element.onmousemove = initialOnMouseMove; - // make dragging and dragged into properties of gd // so that others can look at and modify them gd._dragged = false; @@ -116,10 +117,6 @@ dragElement.init = function init(options) { } function onDone(e) { - // re-enable call to options.setCursor(evt) - initialOnMouseMove = options.element.onmousemove; - if(options.setCursor) options.element.onmousemove = options.setCursor; - dragCover.onmousemove = null; dragCover.onmouseup = null; dragCover.onmouseout = null; @@ -166,10 +163,6 @@ dragElement.init = function init(options) { return Lib.pauseEvent(e); } - // enable call to options.setCursor(evt) - initialOnMouseMove = options.element.onmousemove; - if(options.setCursor) options.element.onmousemove = options.setCursor; - options.element.onmousedown = onStart; options.element.style.pointerEvents = 'all'; }; diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 4cf63dcfc09..bfb083cf6d3 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -392,7 +392,7 @@ drawing.singlePointStyle = function(d, sel, trace, markerScale, lineScale, gd) { }; -drawing.pointStyle = function(s, trace) { +drawing.pointStyle = function(s, trace, gd) { if(!s.size()) return; // allow array marker and marker line colors to be @@ -400,7 +400,6 @@ drawing.pointStyle = function(s, trace) { var marker = trace.marker; var markerScale = drawing.tryColorscale(marker, ''); var lineScale = drawing.tryColorscale(marker, 'line'); - var gd = Lib.getPlotDiv(s.node()); s.each(function(d) { drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale, gd); @@ -423,7 +422,7 @@ drawing.tryColorscale = function(marker, prefix) { // draw text at points var TEXTOFFSETSIGN = {start: 1, end: -1, middle: 0, bottom: 1, top: -1}, LINEEXPAND = 1.3; -drawing.textPointStyle = function(s, trace) { +drawing.textPointStyle = function(s, trace, gd) { s.each(function(d) { var p = d3.select(this), text = d.tx || trace.text; @@ -454,7 +453,7 @@ drawing.textPointStyle = function(s, trace) { d.tc || trace.textfont.color) .attr('text-anchor', h) .text(text) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans, gd); var pgroup = d3.select(this.parentNode), tspans = p.selectAll('tspan.line'), numLines = ((tspans[0].length || 1) - 1) * LINEEXPAND + 1, @@ -611,19 +610,18 @@ drawing.makeTester = function() { // in a reference frame where it isn't translated and its anchor // point is at (0,0) // always returns a copy of the bbox, so the caller can modify it safely -var savedBBoxes = []; +drawing.savedBBoxes = {}; +var savedBBoxesCount = 0; var maxSavedBBoxes = 10000; drawing.bBox = function(node) { // cache elements we've already measured so we don't have to // remeasure the same thing many times - var saveNum = node.attributes['data-bb']; - if(saveNum && saveNum.value) { - return Lib.extendFlat({}, savedBBoxes[saveNum.value]); - } + var hash = nodeHash(node); + var out = drawing.savedBBoxes[hash]; + if(out) return Lib.extendFlat({}, out); - var tester3 = drawing.tester; - var tester = tester3.node(); + var tester = drawing.tester.node(); // copy the node to test into the tester var testNode = node.cloneNode(true); @@ -655,18 +653,41 @@ drawing.bBox = function(node) { // make sure we don't have too many saved boxes, // or a long session could overload on memory // by saving boxes for long-gone elements - if(savedBBoxes.length >= maxSavedBBoxes) { - d3.selectAll('[data-bb]').attr('data-bb', null); - savedBBoxes = []; + if(savedBBoxesCount >= maxSavedBBoxes) { + drawing.savedBBoxes = {}; + maxSavedBBoxes = 0; } // cache this bbox - node.setAttribute('data-bb', savedBBoxes.length); - savedBBoxes.push(bb); + drawing.savedBBoxes[hash] = bb; + savedBBoxesCount++; return Lib.extendFlat({}, bb); }; +// capture everything about a node (at least in our usage) that +// impacts its bounding box, given that bBox clears x, y, and transform +// TODO: is this really everything? Is it worth taking only parts of style, +// so we can share across more changes (like colors)? I guess we can't strip +// colors and stuff from inside innerHTML so maybe not worth bothering outside. +// TODO # 2: this can be long, so could take a lot of memory, do we want to +// hash it? But that can be slow... +// extracting this string from a typical element takes ~3 microsec, where +// doing a simple hash ala https://stackoverflow.com/questions/7616461 +// adds ~15 microsec (nearly all of this is spent in charCodeAt) +// function hash(s) { +// var h = 0; +// for (var i = 0; i < s.length; i++) { +// h = (((h << 5) - h) + s.charCodeAt(i)) | 0; // codePointAt? +// } +// return h; +// } +function nodeHash(node) { + return node.innerHTML + + node.getAttribute('text-anchor') + + node.getAttribute('style'); +} + /* * make a robust clipPath url from a local id * note! We'd better not be exporting from a page diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index b9ebbd59491..4af8366b964 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -161,7 +161,7 @@ exports.loneHover = function loneHover(hoverItem, opts) { outerContainer: outerContainer3 }; - var hoverLabel = createHoverText([pointData], fullOpts); + var hoverLabel = createHoverText([pointData], fullOpts, opts.gd); alignHoverText(hoverLabel, fullOpts.rotateLabels); return hoverLabel.node(); @@ -490,7 +490,7 @@ function _hover(gd, evt, subplot) { commonLabelOpts: fullLayout.hoverlabel }; - var hoverLabels = createHoverText(hoverData, labelOpts); + var hoverLabels = createHoverText(hoverData, labelOpts, gd); hoverAvoidOverlaps(hoverData, rotateLabels ? 'xa' : 'ya'); @@ -523,7 +523,7 @@ function _hover(gd, evt, subplot) { }); } -function createHoverText(hoverData, opts) { +function createHoverText(hoverData, opts, gd) { var hovermode = opts.hovermode; var rotateLabels = opts.rotateLabels; var bgColor = opts.bgColor; @@ -595,7 +595,7 @@ function createHoverText(hoverData, opts) { .attr('data-notex', 1); ltext.text(t0) - .call(svgTextUtils.convertToTspans) + .call(svgTextUtils.convertToTspans, gd) .call(Drawing.setPosition, 0, 0) .selectAll('tspan.line') .call(Drawing.setPosition, 0, 0); @@ -745,7 +745,7 @@ function createHoverText(hoverData, opts) { .call(Drawing.setPosition, 0, 0) .text(text) .attr('data-notex', 1) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans, gd); tx.selectAll('tspan.line') .call(Drawing.setPosition, 0, 0); @@ -761,7 +761,7 @@ function createHoverText(hoverData, opts) { .text(name) .call(Drawing.setPosition, 0, 0) .attr('data-notex', 1) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans, gd); tx2.selectAll('tspan.line') .call(Drawing.setPosition, 0, 0); tx2width = tx2.node().getBoundingClientRect().width + 2 * HOVERTEXTPAD; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 096f4eff4fe..774c5e2e420 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -111,7 +111,7 @@ module.exports = function draw(gd) { traces.enter().append('g').attr('class', 'traces'); traces.exit().remove(); - traces.call(style) + traces.call(style, gd) .style('opacity', function(d) { var trace = d[0].trace; if(Registry.traceIs(trace, 'pie')) { @@ -317,6 +317,7 @@ module.exports = function draw(gd) { dragElement.init({ element: legend.node(), + gd: gd, prepFn: function() { var transform = Drawing.getTranslate(legend); @@ -380,14 +381,14 @@ function drawTexts(g, gd) { .text(name); function textLayout(s) { - svgTextUtils.convertToTspans(s, function() { + svgTextUtils.convertToTspans(s, gd, function() { s.selectAll('tspan.line').attr({x: s.attr('x')}); g.call(computeTextDimensions, gd); }); } if(gd._context.editable && !isPie) { - text.call(svgTextUtils.makeEditable) + text.call(svgTextUtils.makeEditable, {gd: gd}) .call(textLayout) .on('edit', function(text) { this.attr({'data-unformatted': text}); diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 50b1125dd71..c6037e79d07 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -20,7 +20,7 @@ var subTypes = require('../../traces/scatter/subtypes'); var stylePie = require('../../traces/pie/style_one'); -module.exports = function style(s) { +module.exports = function style(s, gd) { s.each(function(d) { var traceGroup = d3.select(this); @@ -58,174 +58,174 @@ module.exports = function style(s) { .each(stylePies) .each(styleLines) .each(stylePoints); -}; - -function styleLines(d) { - var trace = d[0].trace, - showFill = trace.visible && trace.fill && trace.fill !== 'none', - showLine = subTypes.hasLines(trace); - if(trace && trace._module && trace._module.name === 'contourcarpet') { - showLine = trace.contours.showlines; - showFill = trace.contours.coloring === 'fill'; - } + function styleLines(d) { + var trace = d[0].trace, + showFill = trace.visible && trace.fill && trace.fill !== 'none', + showLine = subTypes.hasLines(trace); - var fill = d3.select(this).select('.legendfill').selectAll('path') - .data(showFill ? [d] : []); - fill.enter().append('path').classed('js-fill', true); - fill.exit().remove(); - fill.attr('d', 'M5,0h30v6h-30z') - .call(Drawing.fillGroupStyle); - - var line = d3.select(this).select('.legendlines').selectAll('path') - .data(showLine ? [d] : []); - line.enter().append('path').classed('js-line', true) - .attr('d', 'M5,0h30'); - line.exit().remove(); - line.call(Drawing.lineGroupStyle); -} - -function stylePoints(d) { - var d0 = d[0], - trace = d0.trace, - showMarkers = subTypes.hasMarkers(trace), - showText = subTypes.hasText(trace), - showLines = subTypes.hasLines(trace); - - var dMod, tMod; - - // 'scatter3d' and 'scattergeo' don't use gd.calcdata yet; - // use d0.trace to infer arrayOk attributes - - function boundVal(attrIn, arrayToValFn, bounds) { - var valIn = Lib.nestedProperty(trace, attrIn).get(), - valToBound = (Array.isArray(valIn) && arrayToValFn) ? - arrayToValFn(valIn) : valIn; - - if(bounds) { - if(valToBound < bounds[0]) return bounds[0]; - else if(valToBound > bounds[1]) return bounds[1]; + if(trace && trace._module && trace._module.name === 'contourcarpet') { + showLine = trace.contours.showlines; + showFill = trace.contours.coloring === 'fill'; } - return valToBound; + + var fill = d3.select(this).select('.legendfill').selectAll('path') + .data(showFill ? [d] : []); + fill.enter().append('path').classed('js-fill', true); + fill.exit().remove(); + fill.attr('d', 'M5,0h30v6h-30z') + .call(Drawing.fillGroupStyle); + + var line = d3.select(this).select('.legendlines').selectAll('path') + .data(showLine ? [d] : []); + line.enter().append('path').classed('js-line', true) + .attr('d', 'M5,0h30'); + line.exit().remove(); + line.call(Drawing.lineGroupStyle); } - function pickFirst(array) { return array[0]; } - - // constrain text, markers, etc so they'll fit on the legend - if(showMarkers || showText || showLines) { - var dEdit = {}, - tEdit = {}; - - if(showMarkers) { - dEdit.mc = boundVal('marker.color', pickFirst); - dEdit.mo = boundVal('marker.opacity', Lib.mean, [0.2, 1]); - dEdit.ms = boundVal('marker.size', Lib.mean, [2, 16]); - dEdit.mlc = boundVal('marker.line.color', pickFirst); - dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5]); - tEdit.marker = { - sizeref: 1, - sizemin: 1, - sizemode: 'diameter' - }; - } + function stylePoints(d) { + var d0 = d[0], + trace = d0.trace, + showMarkers = subTypes.hasMarkers(trace), + showText = subTypes.hasText(trace), + showLines = subTypes.hasLines(trace); + + var dMod, tMod; + + // 'scatter3d' and 'scattergeo' don't use gd.calcdata yet; + // use d0.trace to infer arrayOk attributes + + function boundVal(attrIn, arrayToValFn, bounds) { + var valIn = Lib.nestedProperty(trace, attrIn).get(), + valToBound = (Array.isArray(valIn) && arrayToValFn) ? + arrayToValFn(valIn) : valIn; - if(showLines) { - tEdit.line = { - width: boundVal('line.width', pickFirst, [0, 10]) - }; + if(bounds) { + if(valToBound < bounds[0]) return bounds[0]; + else if(valToBound > bounds[1]) return bounds[1]; + } + return valToBound; } - if(showText) { - dEdit.tx = 'Aa'; - dEdit.tp = boundVal('textposition', pickFirst); - dEdit.ts = 10; - dEdit.tc = boundVal('textfont.color', pickFirst); - dEdit.tf = boundVal('textfont.family', pickFirst); + function pickFirst(array) { return array[0]; } + + // constrain text, markers, etc so they'll fit on the legend + if(showMarkers || showText || showLines) { + var dEdit = {}, + tEdit = {}; + + if(showMarkers) { + dEdit.mc = boundVal('marker.color', pickFirst); + dEdit.mo = boundVal('marker.opacity', Lib.mean, [0.2, 1]); + dEdit.ms = boundVal('marker.size', Lib.mean, [2, 16]); + dEdit.mlc = boundVal('marker.line.color', pickFirst); + dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5]); + tEdit.marker = { + sizeref: 1, + sizemin: 1, + sizemode: 'diameter' + }; + } + + if(showLines) { + tEdit.line = { + width: boundVal('line.width', pickFirst, [0, 10]) + }; + } + + if(showText) { + dEdit.tx = 'Aa'; + dEdit.tp = boundVal('textposition', pickFirst); + dEdit.ts = 10; + dEdit.tc = boundVal('textfont.color', pickFirst); + dEdit.tf = boundVal('textfont.family', pickFirst); + } + + dMod = [Lib.minExtend(d0, dEdit)]; + tMod = Lib.minExtend(trace, tEdit); } - dMod = [Lib.minExtend(d0, dEdit)]; - tMod = Lib.minExtend(trace, tEdit); + var ptgroup = d3.select(this).select('g.legendpoints'); + + var pts = ptgroup.selectAll('path.scatterpts') + .data(showMarkers ? dMod : []); + pts.enter().append('path').classed('scatterpts', true) + .attr('transform', 'translate(20,0)'); + pts.exit().remove(); + pts.call(Drawing.pointStyle, tMod, gd); + + // 'mrc' is set in pointStyle and used in textPointStyle: + // constrain it here + if(showMarkers) dMod[0].mrc = 3; + + var txt = ptgroup.selectAll('g.pointtext') + .data(showText ? dMod : []); + txt.enter() + .append('g').classed('pointtext', true) + .append('text').attr('transform', 'translate(20,0)'); + txt.exit().remove(); + txt.selectAll('text').call(Drawing.textPointStyle, tMod, gd); } - var ptgroup = d3.select(this).select('g.legendpoints'); - - var pts = ptgroup.selectAll('path.scatterpts') - .data(showMarkers ? dMod : []); - pts.enter().append('path').classed('scatterpts', true) - .attr('transform', 'translate(20,0)'); - pts.exit().remove(); - pts.call(Drawing.pointStyle, tMod); - - // 'mrc' is set in pointStyle and used in textPointStyle: - // constrain it here - if(showMarkers) dMod[0].mrc = 3; - - var txt = ptgroup.selectAll('g.pointtext') - .data(showText ? dMod : []); - txt.enter() - .append('g').classed('pointtext', true) - .append('text').attr('transform', 'translate(20,0)'); - txt.exit().remove(); - txt.selectAll('text').call(Drawing.textPointStyle, tMod); -} - -function styleBars(d) { - var trace = d[0].trace, - marker = trace.marker || {}, - markerLine = marker.line || {}, - barpath = d3.select(this).select('g.legendpoints') - .selectAll('path.legendbar') - .data(Registry.traceIs(trace, 'bar') ? [d] : []); - barpath.enter().append('path').classed('legendbar', true) - .attr('d', 'M6,6H-6V-6H6Z') - .attr('transform', 'translate(20,0)'); - barpath.exit().remove(); - barpath.each(function(d) { - var p = d3.select(this), - d0 = d[0], - w = (d0.mlw + 1 || markerLine.width + 1) - 1; - - p.style('stroke-width', w + 'px') - .call(Color.fill, d0.mc || marker.color); - - if(w) { - p.call(Color.stroke, d0.mlc || markerLine.color); - } - }); -} - -function styleBoxes(d) { - var trace = d[0].trace, - pts = d3.select(this).select('g.legendpoints') - .selectAll('path.legendbox') - .data(Registry.traceIs(trace, 'box') && trace.visible ? [d] : []); - pts.enter().append('path').classed('legendbox', true) - // if we want the median bar, prepend M6,0H-6 - .attr('d', 'M6,6H-6V-6H6Z') - .attr('transform', 'translate(20,0)'); - pts.exit().remove(); - pts.each(function() { - var w = trace.line.width, - p = d3.select(this); - - p.style('stroke-width', w + 'px') - .call(Color.fill, trace.fillcolor); - - if(w) { - p.call(Color.stroke, trace.line.color); - } - }); -} - -function stylePies(d) { - var trace = d[0].trace, - pts = d3.select(this).select('g.legendpoints') - .selectAll('path.legendpie') - .data(Registry.traceIs(trace, 'pie') && trace.visible ? [d] : []); - pts.enter().append('path').classed('legendpie', true) - .attr('d', 'M6,6H-6V-6H6Z') - .attr('transform', 'translate(20,0)'); - pts.exit().remove(); - - if(pts.size()) pts.call(stylePie, d[0], trace); -} + function styleBars(d) { + var trace = d[0].trace, + marker = trace.marker || {}, + markerLine = marker.line || {}, + barpath = d3.select(this).select('g.legendpoints') + .selectAll('path.legendbar') + .data(Registry.traceIs(trace, 'bar') ? [d] : []); + barpath.enter().append('path').classed('legendbar', true) + .attr('d', 'M6,6H-6V-6H6Z') + .attr('transform', 'translate(20,0)'); + barpath.exit().remove(); + barpath.each(function(d) { + var p = d3.select(this), + d0 = d[0], + w = (d0.mlw + 1 || markerLine.width + 1) - 1; + + p.style('stroke-width', w + 'px') + .call(Color.fill, d0.mc || marker.color); + + if(w) { + p.call(Color.stroke, d0.mlc || markerLine.color); + } + }); + } + + function styleBoxes(d) { + var trace = d[0].trace, + pts = d3.select(this).select('g.legendpoints') + .selectAll('path.legendbox') + .data(Registry.traceIs(trace, 'box') && trace.visible ? [d] : []); + pts.enter().append('path').classed('legendbox', true) + // if we want the median bar, prepend M6,0H-6 + .attr('d', 'M6,6H-6V-6H6Z') + .attr('transform', 'translate(20,0)'); + pts.exit().remove(); + pts.each(function() { + var w = trace.line.width, + p = d3.select(this); + + p.style('stroke-width', w + 'px') + .call(Color.fill, trace.fillcolor); + + if(w) { + p.call(Color.stroke, trace.line.color); + } + }); + } + + function stylePies(d) { + var trace = d[0].trace, + pts = d3.select(this).select('g.legendpoints') + .selectAll('path.legendpie') + .data(Registry.traceIs(trace, 'pie') && trace.visible ? [d] : []); + pts.enter().append('path').classed('legendpie', true) + .attr('d', 'M6,6H-6V-6H6Z') + .attr('transform', 'translate(20,0)'); + pts.exit().remove(); + + if(pts.size()) pts.call(stylePie, d[0], trace); + } +}; diff --git a/src/components/rangeselector/draw.js b/src/components/rangeselector/draw.js index 8dbc8ff4774..901a6977ed6 100644 --- a/src/components/rangeselector/draw.js +++ b/src/components/rangeselector/draw.js @@ -59,7 +59,7 @@ module.exports = function draw(gd) { d.isActive = isActive(axisLayout, d, update); button.call(drawButtonRect, selectorLayout, d); - button.call(drawButtonText, selectorLayout, d); + button.call(drawButtonText, selectorLayout, d, gd); button.on('click', function() { if(gd._dragged) return; @@ -146,9 +146,9 @@ function getFillColor(selectorLayout, d) { selectorLayout.bgcolor; } -function drawButtonText(button, selectorLayout, d) { +function drawButtonText(button, selectorLayout, d, gd) { function textLayout(s) { - svgTextUtils.convertToTspans(s); + svgTextUtils.convertToTspans(s, gd); // TODO do we need anything else here? } diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 2b757ee0a59..bea5bf1108c 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -130,8 +130,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index) { var xa, ya, x2p, y2p, p2x, p2y; var dragOptions = { - setCursor: updateDragMode, element: shapePath.node(), + gd: gd, prepFn: startDrag, doneFn: endDrag }, @@ -140,6 +140,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index) { dragElement.init(dragOptions); + shapePath.node().onmousemove = updateDragMode; + function updateDragMode(evt) { // choose 'move' or 'resize' // based on initial position of cursor within the drag element diff --git a/src/components/sliders/draw.js b/src/components/sliders/draw.js index 46f317e5142..e3309ba1446 100644 --- a/src/components/sliders/draw.js +++ b/src/components/sliders/draw.js @@ -22,7 +22,7 @@ var constants = require('./constants'); module.exports = function draw(gd) { var fullLayout = gd._fullLayout, - sliderData = makeSliderData(fullLayout); + sliderData = makeSliderData(fullLayout, gd); // draw a container for *all* sliders: var sliders = fullLayout._infolayer @@ -97,13 +97,14 @@ module.exports = function draw(gd) { }*/ // This really only just filters by visibility: -function makeSliderData(fullLayout) { +function makeSliderData(fullLayout, gd) { var contOpts = fullLayout[constants.name], sliderData = []; for(var i = 0; i < contOpts.length; i++) { var item = contOpts[i]; if(!item.visible || !item.steps.length) continue; + item.gd = gd; sliderData.push(item); } @@ -302,7 +303,7 @@ function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) { text.call(Drawing.font, sliderOpts.currentvalue.font) .text(str) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans, sliderOpts.gd); Drawing.setTranslate(text, x0, sliderOpts.currentValueHeight); @@ -340,7 +341,7 @@ function drawLabel(item, data, sliderOpts) { text.call(Drawing.font, sliderOpts.font) .text(data.step.label) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans, sliderOpts.gd); return text; } diff --git a/src/components/titles/index.js b/src/components/titles/index.js index 6fa0e64c6af..02287decbcc 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -20,6 +20,7 @@ var Color = require('../color'); var svgTextUtils = require('../../lib/svg_text_utils'); var interactConstants = require('../../constants/interactions'); +var PLACEHOLDER_RE = /Click to enter .+ title/; var Titles = module.exports = {}; @@ -52,29 +53,34 @@ var Titles = module.exports = {}; * title, include here. Otherwise it will go in fullLayout._infolayer */ Titles.draw = function(gd, titleClass, options) { - var cont = options.propContainer, - prop = options.propName, - traceIndex = options.traceIndex, - name = options.dfltName, - avoid = options.avoid || {}, - attributes = options.attributes, - transform = options.transform, - group = options.containerGroup, - - fullLayout = gd._fullLayout, - font = cont.titlefont.family, - fontSize = cont.titlefont.size, - fontColor = cont.titlefont.color, - - opacity = 1, - isplaceholder = false, - txt = cont.title.trim(); + var cont = options.propContainer; + var prop = options.propName; + var traceIndex = options.traceIndex; + var name = options.dfltName; + var avoid = options.avoid || {}; + var attributes = options.attributes; + var transform = options.transform; + var group = options.containerGroup; + + var fullLayout = gd._fullLayout; + var font = cont.titlefont.family; + var fontSize = cont.titlefont.size; + var fontColor = cont.titlefont.color; + + var opacity = 1; + var isplaceholder = false; + var txt = cont.title.trim(); + var editable = gd._context.editable; + if(txt === '') opacity = 0; - if(txt.match(/Click to enter .+ title/)) { + if(txt.match(PLACEHOLDER_RE)) { opacity = 0.2; isplaceholder = true; + if(!editable) txt = ''; } + var elShouldExist = txt || editable; + if(!group) { group = fullLayout._infolayer.selectAll('.g-' + titleClass) .data([0]); @@ -83,7 +89,7 @@ Titles.draw = function(gd, titleClass, options) { } var el = group.selectAll('text') - .data([0]); + .data(elShouldExist ? [0] : []); el.enter().append('text'); el.text(txt) // this is hacky, but convertToTspans uses the class @@ -92,6 +98,9 @@ Titles.draw = function(gd, titleClass, options) { // correct one (only relevant for colorbars, at least // for now) - ie don't use .classed .attr('class', titleClass); + el.exit().remove(); + + if(!elShouldExist) return; function titleLayout(titleEl) { Lib.syncOrAsync([drawTitle, scootTitle], titleEl); @@ -111,7 +120,7 @@ Titles.draw = function(gd, titleClass, options) { 'font-weight': Plots.fontWeight }) .attr(attributes) - .call(svgTextUtils.convertToTspans) + .call(svgTextUtils.convertToTspans, gd) .attr(attributes); titleEl.selectAll('tspan.line') @@ -205,11 +214,11 @@ Titles.draw = function(gd, titleClass, options) { }); } - if(gd._context.editable) { + if(editable) { if(!txt) setPlaceholder(); else el.on('.opacity', null); - el.call(svgTextUtils.makeEditable) + el.call(svgTextUtils.makeEditable, {gd: gd}) .on('edit', function(text) { if(traceIndex !== undefined) Plotly.restyle(gd, prop, text, traceIndex); else Plotly.relayout(gd, prop, text); @@ -224,8 +233,5 @@ Titles.draw = function(gd, titleClass, options) { .attr(attributes); }); } - else if(!txt || txt.match(/Click to enter .+ title/)) { - el.remove(); - } el.classed('js-placeholder', isplaceholder); }; diff --git a/src/components/updatemenus/draw.js b/src/components/updatemenus/draw.js index adf32f1f486..118335fed1a 100644 --- a/src/components/updatemenus/draw.js +++ b/src/components/updatemenus/draw.js @@ -202,7 +202,7 @@ function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) { }; header - .call(drawItem, menuOpts, headerOpts) + .call(drawItem, menuOpts, headerOpts, gd) .call(setItemPosition, menuOpts, posOpts, positionOverrides); // draw drop arrow at the right edge @@ -322,7 +322,7 @@ function drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) { var button = d3.select(this); button - .call(drawItem, menuOpts, buttonOpts) + .call(drawItem, menuOpts, buttonOpts, gd) .call(setItemPosition, menuOpts, posOpts); button.on('click', function() { @@ -434,9 +434,9 @@ function hideScrollBox(scrollBox) { } } -function drawItem(item, menuOpts, itemOpts) { +function drawItem(item, menuOpts, itemOpts, gd) { item.call(drawItemRect, menuOpts) - .call(drawItemText, menuOpts, itemOpts); + .call(drawItemText, menuOpts, itemOpts, gd); } function drawItemRect(item, menuOpts) { @@ -456,7 +456,7 @@ function drawItemRect(item, menuOpts) { .style('stroke-width', menuOpts.borderwidth + 'px'); } -function drawItemText(item, menuOpts, itemOpts) { +function drawItemText(item, menuOpts, itemOpts, gd) { var text = item.selectAll('text') .data([0]); @@ -467,7 +467,7 @@ function drawItemText(item, menuOpts, itemOpts) { text.call(Drawing.font, menuOpts.font) .text(itemOpts.label) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans, gd); } function styleButtons(buttons, menuOpts) { @@ -518,7 +518,7 @@ function findDimensions(gd, menuOpts) { fakeButtons.each(function(buttonOpts, i) { var button = d3.select(this); - button.call(drawItem, menuOpts, buttonOpts); + button.call(drawItem, menuOpts, buttonOpts, gd); var text = button.select('.' + constants.itemTextClassName), tspans = text.selectAll('tspan'); diff --git a/src/lib/index.js b/src/lib/index.js index 85e6fb7299f..8518d176d1d 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -481,13 +481,6 @@ lib.containsAny = function(s, fragments) { return false; }; -// get the parent Plotly plot of any element. Whoo jquery-free tree climbing! -lib.getPlotDiv = function(el) { - for(; el && el.removeAttribute; el = el.parentNode) { - if(lib.isPlotDiv(el)) return el; - } -}; - lib.isPlotDiv = function(el) { var el3 = d3.select(el); return el3.node() instanceof HTMLElement && diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index 10242e4a639..e1708b5156a 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -79,29 +79,25 @@ function getSize(_selection, _dimension) { return _selection.node().getBoundingClientRect()[_dimension]; } -exports.convertToTspans = function(_context, _callback) { +exports.convertToTspans = function(_context, gd, _callback) { var str = _context.text(); var converted = convertToSVG(str); - var that = _context; // Until we get tex integrated more fully (so it can be used along with non-tex) // allow some elements to prohibit it by attaching 'data-notex' to the original - var tex = (!that.attr('data-notex')) && converted.match(/([^$]*)([$]+[^$]*[$]+)([^$]*)/); + var tex = (!_context.attr('data-notex')) && converted.match(/([^$]*)([$]+[^$]*[$]+)([^$]*)/); var result = str; - var parent = d3.select(that.node().parentNode); + var parent = d3.select(_context.node().parentNode); if(parent.empty()) return; - var svgClass = (that.attr('class')) ? that.attr('class').split(' ')[0] : 'text'; + var svgClass = (_context.attr('class')) ? _context.attr('class').split(' ')[0] : 'text'; svgClass += '-math'; parent.selectAll('svg.' + svgClass).remove(); parent.selectAll('g.' + svgClass + '-group').remove(); _context.style({visibility: null}); - for(var up = _context.node(); up && up.removeAttribute; up = up.parentNode) { - up.removeAttribute('data-bb'); - } function showText() { if(!parent.empty()) { - svgClass = that.attr('class') + '-math'; + svgClass = _context.attr('class') + '-math'; parent.select('svg.' + svgClass).remove(); } _context.text('') @@ -122,14 +118,13 @@ exports.convertToTspans = function(_context, _callback) { _context.style('pointer-events', 'all'); } - if(_callback) _callback.call(that); + if(_callback) _callback.call(_context); } if(tex) { - var gd = Lib.getPlotDiv(that.node()); ((gd && gd._promises) || []).push(new Promise(function(resolve) { - that.style({visibility: 'hidden'}); - var config = {fontSize: parseInt(that.style('font-size'), 10)}; + _context.style({visibility: 'hidden'}); + var config = {fontSize: parseInt(_context.style('font-size'), 10)}; texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) { parent.selectAll('svg.' + svgClass).remove(); @@ -161,36 +156,36 @@ exports.convertToTspans = function(_context, _callback) { }) .style({overflow: 'visible', 'pointer-events': 'none'}); - var fill = that.style('fill') || 'black'; + var fill = _context.style('fill') || 'black'; newSvg.select('g').attr({fill: fill, stroke: fill}); var newSvgW = getSize(newSvg, 'width'), newSvgH = getSize(newSvg, 'height'), - newX = +that.attr('x') - newSvgW * - {start: 0, middle: 0.5, end: 1}[that.attr('text-anchor') || 'start'], + newX = +_context.attr('x') - newSvgW * + {start: 0, middle: 0.5, end: 1}[_context.attr('text-anchor') || 'start'], // font baseline is about 1/4 fontSize below centerline - textHeight = parseInt(that.style('font-size'), 10) || - getSize(that, 'height'), + textHeight = parseInt(_context.style('font-size'), 10) || + getSize(_context, 'height'), dy = -textHeight / 4; if(svgClass[0] === 'y') { mathjaxGroup.attr({ - transform: 'rotate(' + [-90, +that.attr('x'), +that.attr('y')] + + transform: 'rotate(' + [-90, +_context.attr('x'), +_context.attr('y')] + ') translate(' + [-newSvgW / 2, dy - newSvgH / 2] + ')' }); - newSvg.attr({x: +that.attr('x'), y: +that.attr('y')}); + newSvg.attr({x: +_context.attr('x'), y: +_context.attr('y')}); } else if(svgClass[0] === 'l') { - newSvg.attr({x: that.attr('x'), y: dy - (newSvgH / 2)}); + newSvg.attr({x: _context.attr('x'), y: dy - (newSvgH / 2)}); } else if(svgClass[0] === 'a') { newSvg.attr({x: 0, y: dy}); } else { - newSvg.attr({x: newX, y: (+that.attr('y') + dy - newSvgH / 2)}); + newSvg.attr({x: newX, y: (+_context.attr('y') + dy - newSvgH / 2)}); } - if(_callback) _callback.call(that, mathjaxGroup); + if(_callback) _callback.call(_context, mathjaxGroup); resolve(mathjaxGroup); }); })); @@ -528,28 +523,41 @@ function alignHTMLWith(_base, container, options) { }; } -// Editable title +/* + * Editable title + * @param {d3.selection} context: the element being edited. Normally text, + * but if it isn't, you should provide the styling options + * @param {object} options: + * @param {div} options.gd: graphDiv + * @param {d3.selection} options.delegate: item to bind events to if not this + * @param {boolean} options.immediate: start editing now (true) or on click (false, default) + * @param {string} options.fill: font color if not as shown + * @param {string} options.background: background color if not as shown + * @param {string} options.text: initial text, if not as shown + * @param {string} options.horizontalAlign: alignment of the edit box wrt. the bound element + * @param {string} options.verticalAlign: alignment of the edit box wrt. the bound element + */ -exports.makeEditable = function(context, _delegate, options) { - if(!options) options = {}; - var that = this; +exports.makeEditable = function(context, options) { + var gd = options.gd; + var _delegate = options.delegate; var dispatch = d3.dispatch('edit', 'input', 'cancel'); - var textSelection = d3.select(this.node()) - .style({'pointer-events': 'all'}); + var handlerElement = _delegate || context; + + context.style({'pointer-events': _delegate ? 'none' : 'all'}); - var handlerElement = _delegate || textSelection; - if(_delegate) textSelection.style({'pointer-events': 'none'}); + if(context.size() !== 1) throw new Error('boo'); function handleClick() { appendEditable(); - that.style({opacity: 0}); + context.style({opacity: 0}); // also hide any mathjax svg var svgClass = handlerElement.attr('class'), mathjaxClass; if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group'; else mathjaxClass = '[class*=-math-group]'; if(mathjaxClass) { - d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0}); + d3.select(context.node().parentNode).select(mathjaxClass).style({opacity: 0}); } } @@ -564,63 +572,62 @@ exports.makeEditable = function(context, _delegate, options) { } function appendEditable() { - var gd = Lib.getPlotDiv(that.node()), - plotDiv = d3.select(gd), + var plotDiv = d3.select(gd), container = plotDiv.select('.svg-container'), div = container.append('div'); div.classed('plugin-editable editable', true) .style({ position: 'absolute', - 'font-family': that.style('font-family') || 'Arial', - 'font-size': that.style('font-size') || 12, - color: options.fill || that.style('fill') || 'black', + 'font-family': context.style('font-family') || 'Arial', + 'font-size': context.style('font-size') || 12, + color: options.fill || context.style('fill') || 'black', opacity: 1, 'background-color': options.background || 'transparent', outline: '#ffffff33 1px solid', - margin: [-parseFloat(that.style('font-size')) / 8 + 1, 0, 0, -1].join('px ') + 'px', + margin: [-parseFloat(context.style('font-size')) / 8 + 1, 0, 0, -1].join('px ') + 'px', padding: '0', 'box-sizing': 'border-box' }) .attr({contenteditable: true}) - .text(options.text || that.attr('data-unformatted')) - .call(alignHTMLWith(that, container, options)) + .text(options.text || context.attr('data-unformatted')) + .call(alignHTMLWith(context, container, options)) .on('blur', function() { gd._editing = false; - that.text(this.textContent) + context.text(this.textContent) .style({opacity: 1}); var svgClass = d3.select(this).attr('class'), mathjaxClass; if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group'; else mathjaxClass = '[class*=-math-group]'; if(mathjaxClass) { - d3.select(that.node().parentNode).select(mathjaxClass).style({opacity: 0}); + d3.select(context.node().parentNode).select(mathjaxClass).style({opacity: 0}); } var text = this.textContent; d3.select(this).transition().duration(0).remove(); d3.select(document).on('mouseup', null); - dispatch.edit.call(that, text); + dispatch.edit.call(context, text); }) .on('focus', function() { - var context = this; + var editDiv = this; gd._editing = true; d3.select(document).on('mouseup', function() { - if(d3.event.target === context) return false; + if(d3.event.target === editDiv) return false; if(document.activeElement === div.node()) div.node().blur(); }); }) .on('keyup', function() { if(d3.event.which === 27) { gd._editing = false; - that.style({opacity: 1}); + context.style({opacity: 1}); d3.select(this) .style({opacity: 0}) .on('blur', function() { return false; }) .transition().remove(); - dispatch.cancel.call(that, this.textContent); + dispatch.cancel.call(context, this.textContent); } else { - dispatch.input.call(that, this.textContent); - d3.select(this).call(alignHTMLWith(that, container, options)); + dispatch.input.call(context, this.textContent); + d3.select(this).call(alignHTMLWith(context, container, options)); } }) .on('keydown', function() { @@ -632,5 +639,5 @@ exports.makeEditable = function(context, _delegate, options) { if(options.immediate) handleClick(); else handlerElement.on('click', handleClick); - return d3.rebind(this, dispatch, 'on'); + return d3.rebind(context, dispatch, 'on'); }; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 709fb9d2251..f993e5aaba0 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -191,8 +191,7 @@ Plotly.plot = function(gd, data, layout, config) { } return Lib.syncOrAsync([ - subroutines.layoutStyles, - drawAxes + subroutines.layoutStyles ], gd); } @@ -369,7 +368,6 @@ Plotly.plot = function(gd, data, layout, config) { drawFramework, marginPushers, marginPushersAgain, - initInteractions, positionAndAutorange, subroutines.layoutStyles, drawAxes, @@ -473,7 +471,7 @@ function plotPolar(gd, data, layout) { var placeholderText = 'Click to enter title'; var titleLayout = function() { - this.call(svgTextUtils.convertToTspans); + this.call(svgTextUtils.convertToTspans, gd); // TODO: html/mathjax // TODO: center title }; @@ -499,7 +497,7 @@ function plotPolar(gd, data, layout) { } var setContenteditable = function() { - this.call(svgTextUtils.makeEditable) + this.call(svgTextUtils.makeEditable, {gd: gd}) .on('edit', function(text) { gd.framework({layout: {title: text}}); this.attr({'data-unformatted': text}) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 32536757cec..18bdd7693ff 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1816,7 +1816,7 @@ axes.doTicks = function(gd, axid, skipTitle) { .call(Drawing.setPosition, labelx(d), labely(d)) .call(Drawing.font, d.font, d.fontSize, d.fontColor) .text(d.text) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans, gd); newPromise = gd._promises[newPromise]; if(newPromise) { // if we have an async label, we'll deal with that diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 7be44097c2a..0a91de5f745 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -130,7 +130,6 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { element: dragger, gd: gd, plotinfo: plotinfo, - doubleclick: doubleClick, prepFn: function(e, startX, startY) { var dragModeNow = gd._fullLayout.dragmode; @@ -319,7 +318,8 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(gd._context.showAxisRangeEntryBoxes) { d3.select(dragger) - .call(svgTextUtils.makeEditable, null, { + .call(svgTextUtils.makeEditable, { + gd: gd, immediate: true, background: fullLayout.paper_bgcolor, text: String(initialText), diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index 00fc49969f0..d328b8959c2 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -670,7 +670,8 @@ proto.draw = function() { fontSize: Fx.castHoverOption(trace, ptNumber, 'font.size'), fontColor: Fx.castHoverOption(trace, ptNumber, 'font.color') }, { - container: this.svgContainer + container: this.svgContainer, + gd: this.graphDiv }); } } diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index db6c2e7dbbb..fdd678ace2b 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -98,7 +98,8 @@ function render(scene) { fontSize: Fx.castHoverOption(trace, ptNumber, 'font.size'), fontColor: Fx.castHoverOption(trace, ptNumber, 'font.color') }, { - container: svgContainer + container: svgContainer, + gd: scene.graphDiv }); } diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 8f64743e833..9cf578948fa 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -146,14 +146,13 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { .attr({ 'class': 'bartext', transform: '', - 'data-bb': '', 'text-anchor': 'middle', x: 0, y: 0 }) .call(Drawing.font, textFont); - textSelection.call(svgTextUtils.convertToTspans); + textSelection.call(svgTextUtils.convertToTspans, gd); textSelection.selectAll('tspan.line').attr({x: 0, y: 0}); return textSelection; diff --git a/src/traces/bar/style.js b/src/traces/bar/style.js index d0fc54e3429..633a89dbc7d 100644 --- a/src/traces/bar/style.js +++ b/src/traces/bar/style.js @@ -67,9 +67,6 @@ module.exports = function style(gd) { p.call(Color.stroke, lineColor); } }); - // TODO: text markers on bars, either extra text or just bar values - // d3.select(this).selectAll('text') - // .call(Drawing.textPointStyle,d.t||d[0].t); }); s.call(ErrorBars.style); diff --git a/src/traces/box/style.js b/src/traces/box/style.js index cb187ebedca..7dd3ae4a4c7 100644 --- a/src/traces/box/style.js +++ b/src/traces/box/style.js @@ -32,6 +32,6 @@ module.exports = function style(gd) { }) .call(Color.stroke, trace.line.color); d3.select(this).selectAll('g.points path') - .call(Drawing.pointStyle, trace); + .call(Drawing.pointStyle, trace, gd); }); }; diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 0d12b8f5b67..52fce7b0e4a 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -138,7 +138,8 @@ module.exports = function plot(gd, cdpie) { fontColor: Fx.castHoverOption(trace, pt.i, 'font.color') }, { container: fullLayout2._hoverlayer.node(), - outerContainer: fullLayout2._paper.node() + outerContainer: fullLayout2._paper.node(), + gd: gd }); Fx.hover(gd, evt, 'pie'); @@ -250,14 +251,13 @@ module.exports = function plot(gd, cdpie) { .attr({ 'class': 'slicetext', transform: '', - 'data-bb': '', 'text-anchor': 'middle', x: 0, y: 0 }) .call(Drawing.font, textPosition === 'outside' ? trace.outsidetextfont : trace.insidetextfont) - .call(svgTextUtils.convertToTspans); + .call(svgTextUtils.convertToTspans, gd); sliceText.selectAll('tspan.line').attr({x: 0, y: 0}); // position the text relative to the slice @@ -273,7 +273,6 @@ module.exports = function plot(gd, cdpie) { sliceText.call(Drawing.font, trace.outsidetextfont); if(trace.outsidetextfont.family !== trace.insidetextfont.family || trace.outsidetextfont.size !== trace.insidetextfont.size) { - sliceText.attr({'data-bb': ''}); textBB = Drawing.bBox(sliceText.node()); } transform = transformOutsideText(textBB, pt); diff --git a/src/traces/sankey/plot.js b/src/traces/sankey/plot.js index 69038858a9b..14f97ebb787 100644 --- a/src/traces/sankey/plot.js +++ b/src/traces/sankey/plot.js @@ -155,7 +155,8 @@ module.exports = function plot(gd, calcData) { idealAlign: d3.event.x < hoverCenterX ? 'right' : 'left' }, { container: fullLayout._hoverlayer.node(), - outerContainer: fullLayout._paper.node() + outerContainer: fullLayout._paper.node(), + gd: gd }); makeTranslucent(tooltip, 0.65); @@ -210,7 +211,8 @@ module.exports = function plot(gd, calcData) { idealAlign: 'left' }, { container: fullLayout._hoverlayer.node(), - outerContainer: fullLayout._paper.node() + outerContainer: fullLayout._paper.node(), + gd: gd }); makeTranslucent(tooltip, 0.85); diff --git a/src/traces/scatter/plot.js b/src/traces/scatter/plot.js index 8357a1210d4..0a877aee4de 100644 --- a/src/traces/scatter/plot.js +++ b/src/traces/scatter/plot.js @@ -425,7 +425,7 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition if(hasTransition) { enter - .call(Drawing.pointStyle, trace) + .call(Drawing.pointStyle, trace, gd) .call(Drawing.translatePoints, xa, ya, trace) .style('opacity', 0) .transition() @@ -479,7 +479,7 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition }); join.selectAll('text') - .call(Drawing.textPointStyle, trace) + .call(Drawing.textPointStyle, trace, gd) .each(function(d) { // This just *has* to be totally custom becuase of SVG text positioning :( diff --git a/src/traces/scatter/style.js b/src/traces/scatter/style.js index 9f0c17a935d..b4475f53d6c 100644 --- a/src/traces/scatter/style.js +++ b/src/traces/scatter/style.js @@ -28,10 +28,10 @@ module.exports = function style(gd) { var pts = el.selectAll('path.point'); var trace = d.trace || d[0].trace; - pts.call(Drawing.pointStyle, trace); + pts.call(Drawing.pointStyle, trace, gd); el.selectAll('text') - .call(Drawing.textPointStyle, trace); + .call(Drawing.textPointStyle, trace, gd); }); s.selectAll('g.trace path.js-line') diff --git a/src/traces/scattergeo/plot.js b/src/traces/scattergeo/plot.js index 10451a247be..094e2b60e84 100644 --- a/src/traces/scattergeo/plot.js +++ b/src/traces/scattergeo/plot.js @@ -114,9 +114,9 @@ function style(geo) { group = d3.select(this); group.selectAll('path.point') - .call(Drawing.pointStyle, trace); + .call(Drawing.pointStyle, trace, geo.graphDiv); group.selectAll('text') - .call(Drawing.textPointStyle, trace); + .call(Drawing.textPointStyle, trace, geo.graphDiv); }); // this part is incompatible with Drawing.lineGroupStyle diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 1104ddff8fd..3b0aa922322 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -3,15 +3,14 @@ var Plotly = require('@lib/index'); var Bar = require('@src/traces/bar'); var Lib = require('@src/lib'); var Plots = require('@src/plots/plots'); +var Drawing = require('@src/components/drawing'); -var PlotlyInternal = require('@src/plotly'); -var Axes = PlotlyInternal.Axes; +var Axes = require('@src/plots/cartesian/axes'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); var customMatchers = require('../assets/custom_matchers'); -var failTest = require('../assets/fail_test'); describe('Bar.supplyDefaults', function() { 'use strict'; @@ -855,9 +854,9 @@ describe('A bar plot', function() { } expect(foundTextNodes).toBe(true); - - done(); - }); + }) + .catch(fail) + .then(done); }); it('should show bar texts (outside case)', function(done) { @@ -889,9 +888,9 @@ describe('A bar plot', function() { } expect(foundTextNodes).toBe(true); - - done(); - }); + }) + .catch(fail) + .then(done); }); it('should show bar texts (horizontal case)', function(done) { @@ -922,9 +921,9 @@ describe('A bar plot', function() { } expect(foundTextNodes).toBe(true); - - done(); - }); + }) + .catch(fail) + .then(done); }); it('should show bar texts (barnorm case)', function(done) { @@ -957,9 +956,9 @@ describe('A bar plot', function() { } expect(foundTextNodes).toBe(true); - - done(); - }); + }) + .catch(fail) + .then(done); }); it('should be able to restyle', function(done) { @@ -1045,6 +1044,17 @@ describe('A bar plot', function() { assertTextIsInsidePath(text20, path20); // inside assertTextIsBelowPath(text30, path30); // outside + // clear bounding box cache - somehow when you cache + // text size too early sometimes it changes later... + // we've had this issue before, where we've had to + // redraw annotations to get final sizes, I wish we + // could get some signal that fonts are really ready + // and not start drawing until then (or invalidate + // the bbox cache when that happens?) + // without this change, we get an error at + // assertTextIsInsidePath(text30, path30); + Drawing.savedBBoxes = {}; + return Plotly.restyle(gd, 'textposition', 'inside'); }).then(function() { var cd = gd.calcdata; @@ -1096,9 +1106,9 @@ describe('A bar plot', function() { assertTextIsInsidePath(text12, path12); // inside assertTextIsInsidePath(text20, path20); // inside assertTextIsInsidePath(text30, path30); // inside - - done(); - }); + }) + .catch(fail) + .then(done); }); it('should coerce text-related attributes', function(done) { @@ -1178,9 +1188,9 @@ describe('A bar plot', function() { assertTextFont(textNodes[0], expected.insidetextfont, 0); assertTextFont(textNodes[1], expected.outsidetextfont, 1); assertTextFont(textNodes[2], expected.insidetextfont, 2); - - done(); - }); + }) + .catch(fail) + .then(done); }); }); @@ -1237,7 +1247,9 @@ describe('bar hover', function() { var mock = Lib.extendDeep({}, require('@mocks/11.json')); - Plotly.plot(gd, mock.data, mock.layout).then(done); + Plotly.plot(gd, mock.data, mock.layout) + .catch(fail) + .then(done); }); it('should return the correct hover point data (case x)', function() { @@ -1261,7 +1273,9 @@ describe('bar hover', function() { var mock = Lib.extendDeep({}, require('@mocks/bar_attrs_group_norm.json')); - Plotly.plot(gd, mock.data, mock.layout).then(done); + Plotly.plot(gd, mock.data, mock.layout) + .catch(fail) + .then(done); }); it('should return the correct hover point data (case y)', function() { @@ -1376,7 +1390,7 @@ describe('bar hover', function() { expect(out).toBe(false, hoverSpec); }); }) - .catch(failTest) + .catch(fail) .then(done); }); @@ -1414,7 +1428,7 @@ describe('bar hover', function() { expect(out.style).toEqual([1, 'red', 200, 1]); assertPos(out.pos, [203, 304, 168, 168]); }) - .catch(failTest) + .catch(fail) .then(done); }); }); diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 11106fa89c3..3bd985c5b9c 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -27,7 +27,9 @@ describe('zoom box element', function() { var mockCopy = Lib.extendDeep({}, mock); mockCopy.layout.dragmode = 'zoom'; - Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .catch(failTest) + .then(done); }); afterEach(destroyGraphDiv); @@ -74,9 +76,9 @@ describe('main plot pan', function() { relayoutCallback = jasmine.createSpy('relayoutCallback'); gd.on('plotly_relayout', relayoutCallback); - - done(); - }); + }) + .catch(failTest) + .then(done); }); afterEach(destroyGraphDiv); @@ -107,7 +109,8 @@ describe('main plot pan', function() { expect(gd.layout.xaxis.range).toBeCloseToArray(originalX, precision); expect(gd.layout.yaxis.range).toBeCloseToArray(originalY, precision); - setTimeout(function() { + delay(MODEBAR_DELAY)() + .then(function() { expect(relayoutCallback).toHaveBeenCalledTimes(1); relayoutCallback.calls.reset(); @@ -165,25 +168,26 @@ describe('main plot pan', function() { expect(gd.layout.xaxis.range).toBeCloseToArray(originalX, precision); expect(gd.layout.yaxis.range).toBeCloseToArray(originalY, precision); - - setTimeout(function() { - - expect(relayoutCallback).toHaveBeenCalledTimes(6); // X and back; Y and back; XY and back - - done(); - - }, MODEBAR_DELAY); - - }, MODEBAR_DELAY); + }) + .then(delay(MODEBAR_DELAY)) + .then(function() { + // X and back; Y and back; XY and back + expect(relayoutCallback).toHaveBeenCalledTimes(6); + }) + .catch(failTest) + .then(done); }); }); describe('axis zoom/pan and main plot zoom', function() { var gd; + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + beforeEach(function() { gd = createGraphDiv(); - jasmine.addMatchers(customMatchers); }); afterEach(destroyGraphDiv); @@ -208,6 +212,7 @@ describe('axis zoom/pan and main plot zoom', function() { // each subplot is 200x200 px // if constrainScales is used, x/x2/y/y2 are linked, as are x3/y3 // layoutEdits are other changes to make to the layout + var data = [ {y: [0, 1, 2]}, {y: [0, 1, 2], xaxis: 'x2'}, @@ -239,13 +244,9 @@ describe('axis zoom/pan and main plot zoom', function() { if(layoutEdits) Lib.extendDeep(layout, layoutEdits); - return Plotly.newPlot(gd, data, layout, config).then(function() { - [ - 'xaxis', 'yaxis', 'xaxis2', 'yaxis2', 'xaxis3', 'yaxis3' - ].forEach(function(axName) { - expect(gd._fullLayout[axName].range).toEqual(initialRange); - }); - + return Plotly.newPlot(gd, data, layout, config) + .then(checkRanges({}, 'initial')) + .then(function() { expect(Object.keys(gd._fullLayout._plots)) .toEqual(['xy', 'xy2', 'x2y', 'x3y3']); @@ -273,10 +274,16 @@ describe('axis zoom/pan and main plot zoom', function() { } function doDblClick(subplot, directions) { - return function() { return doubleClick(getDragger(subplot, directions)); }; + return function() { + gd._mouseDownTime = 0; // ensure independence from any previous clicks + return doubleClick(getDragger(subplot, directions)); + }; } - function checkRanges(newRanges) { + function checkRanges(newRanges, msg) { + msg = msg || ''; + if(msg) msg = ' - ' + msg; + return function() { var allRanges = { xaxis: initialRange.slice(), @@ -289,8 +296,8 @@ describe('axis zoom/pan and main plot zoom', function() { Lib.extendDeep(allRanges, newRanges); for(var axName in allRanges) { - expect(gd.layout[axName].range).toBeCloseToArray(allRanges[axName], 3, axName); - expect(gd._fullLayout[axName].range).toBeCloseToArray(gd.layout[axName].range, 6, axName); + expect(gd.layout[axName].range).toBeCloseToArray(allRanges[axName], 3, axName + msg); + expect(gd._fullLayout[axName].range).toBeCloseToArray(gd.layout[axName].range, 6, axName + msg); } }; } @@ -299,45 +306,45 @@ describe('axis zoom/pan and main plot zoom', function() { makePlot() // zoombox into a small point - drag starts from the center unless you specify otherwise .then(doDrag('xy', 'nsew', 100, -50)) - .then(checkRanges({xaxis: [1, 2], yaxis: [1, 1.5]})) + .then(checkRanges({xaxis: [1, 2], yaxis: [1, 1.5]}, 'zoombox')) // first dblclick reverts to saved ranges .then(doDblClick('xy', 'nsew')) - .then(checkRanges()) + .then(checkRanges({}, 'dblclick #1')) // next dblclick autoscales (just that plot) .then(doDblClick('xy', 'nsew')) - .then(checkRanges({xaxis: autoRange, yaxis: autoRange})) + .then(checkRanges({xaxis: autoRange, yaxis: autoRange}, 'dblclick #2')) // dblclick on one axis reverts just that axis to saved .then(doDblClick('xy', 'ns')) - .then(checkRanges({xaxis: autoRange})) + .then(checkRanges({xaxis: autoRange}, 'dblclick y')) // dblclick the plot at this point (one axis default, the other autoscaled) // and the whole thing is reverted to default .then(doDblClick('xy', 'nsew')) - .then(checkRanges()) + .then(checkRanges({}, 'dblclick #3')) // 1D zoombox - use the linked subplots .then(doDrag('xy2', 'nsew', -100, 0)) - .then(checkRanges({xaxis: [0, 1]})) + .then(checkRanges({xaxis: [0, 1]}, 'xy2 zoombox')) .then(doDrag('x2y', 'nsew', 0, 50)) - .then(checkRanges({xaxis: [0, 1], yaxis: [0.5, 1]})) + .then(checkRanges({xaxis: [0, 1], yaxis: [0.5, 1]}, 'x2y zoombox')) // dblclick on linked subplots just changes the linked axis .then(doDblClick('xy2', 'nsew')) - .then(checkRanges({yaxis: [0.5, 1]})) + .then(checkRanges({yaxis: [0.5, 1]}, 'dblclick xy2')) .then(doDblClick('x2y', 'nsew')) - .then(checkRanges()) + .then(checkRanges({}, 'dblclick x2y')) // drag on axis ends - all these 1D draggers the opposite axis delta is irrelevant .then(doDrag('xy2', 'n', 53, 100)) - .then(checkRanges({yaxis2: [0, 4]})) + .then(checkRanges({yaxis2: [0, 4]}, 'drag y2n')) .then(doDrag('xy', 's', 53, -100)) - .then(checkRanges({yaxis: [-2, 2], yaxis2: [0, 4]})) + .then(checkRanges({yaxis: [-2, 2], yaxis2: [0, 4]}, 'drag ys')) // expanding drag is highly nonlinear .then(doDrag('x2y', 'e', 50, 53)) - .then(checkRanges({yaxis: [-2, 2], yaxis2: [0, 4], xaxis2: [0, 0.8751]})) + .then(checkRanges({yaxis: [-2, 2], yaxis2: [0, 4], xaxis2: [0, 0.8751]}, 'drag x2e')) .then(doDrag('x2y', 'w', -50, 53)) - .then(checkRanges({yaxis: [-2, 2], yaxis2: [0, 4], xaxis2: [0.4922, 0.8751]})) + .then(checkRanges({yaxis: [-2, 2], yaxis2: [0, 4], xaxis2: [0.4922, 0.8751]}, 'drag x2w')) // reset all from the modebar .then(function() { selectButton(gd._fullLayout._modeBar, 'resetScale2d').click(); }) - .then(checkRanges()) + .then(checkRanges({}, 'final reset')) .catch(failTest) .then(done); }); @@ -346,20 +353,20 @@ describe('axis zoom/pan and main plot zoom', function() { makePlot() // drag axis middles .then(doDrag('x3y3', 'ew', 100, 0)) - .then(checkRanges({xaxis3: [-1, 1]})) + .then(checkRanges({xaxis3: [-1, 1]}, 'drag x3ew')) .then(doDrag('x3y3', 'ns', 53, 100)) - .then(checkRanges({xaxis3: [-1, 1], yaxis3: [1, 3]})) + .then(checkRanges({xaxis3: [-1, 1], yaxis3: [1, 3]}, 'drag y3ns')) // drag corners .then(doDrag('x3y3', 'ne', -100, 100)) - .then(checkRanges({xaxis3: [-1, 3], yaxis3: [1, 5]})) + .then(checkRanges({xaxis3: [-1, 3], yaxis3: [1, 5]}, 'zoom x3y3ne')) .then(doDrag('x3y3', 'sw', 100, -100)) - .then(checkRanges({xaxis3: [-5, 3], yaxis3: [-3, 5]})) + .then(checkRanges({xaxis3: [-5, 3], yaxis3: [-3, 5]}, 'zoom x3y3sw')) .then(doDrag('x3y3', 'nw', -50, -50)) - .then(checkRanges({xaxis3: [-0.5006, 3], yaxis3: [-3, 0.5006]})) + .then(checkRanges({xaxis3: [-0.5006, 3], yaxis3: [-3, 0.5006]}, 'zoom x3y3nw')) .then(doDrag('x3y3', 'se', 50, 50)) - .then(checkRanges({xaxis3: [-0.5006, 1.0312], yaxis3: [-1.0312, 0.5006]})) + .then(checkRanges({xaxis3: [-0.5006, 1.0312], yaxis3: [-1.0312, 0.5006]}, 'zoom x3y3se')) .then(doDblClick('x3y3', 'nsew')) - .then(checkRanges()) + .then(checkRanges({}, 'reset x3y3')) // scroll wheel .then(function() { var mainDrag = getDragger('xy', 'nsew'); @@ -367,21 +374,21 @@ describe('axis zoom/pan and main plot zoom', function() { mouseEvent('scroll', mainDragCoords.x, mainDragCoords.y, {deltaY: 20, element: mainDrag}); }) .then(delay(constants.REDRAWDELAY + 10)) - .then(checkRanges({xaxis: [-0.4428, 2], yaxis: [0, 2.4428]})) + .then(checkRanges({xaxis: [-0.4428, 2], yaxis: [0, 2.4428]}, 'xy main scroll')) .then(function() { var ewDrag = getDragger('xy', 'ew'); var ewDragCoords = getNodeCoords(ewDrag); mouseEvent('scroll', ewDragCoords.x - 50, ewDragCoords.y, {deltaY: -20, element: ewDrag}); }) .then(delay(constants.REDRAWDELAY + 10)) - .then(checkRanges({xaxis: [-0.3321, 1.6679], yaxis: [0, 2.4428]})) + .then(checkRanges({xaxis: [-0.3321, 1.6679], yaxis: [0, 2.4428]}, 'x scroll')) .then(function() { var nsDrag = getDragger('xy', 'ns'); var nsDragCoords = getNodeCoords(nsDrag); mouseEvent('scroll', nsDragCoords.x, nsDragCoords.y - 50, {deltaY: -20, element: nsDrag}); }) .then(delay(constants.REDRAWDELAY + 10)) - .then(checkRanges({xaxis: [-0.3321, 1.6679], yaxis: [0.3321, 2.3321]})) + .then(checkRanges({xaxis: [-0.3321, 1.6679], yaxis: [0.3321, 2.3321]}, 'y scroll')) .catch(failTest) .then(done); }); @@ -390,29 +397,29 @@ describe('axis zoom/pan and main plot zoom', function() { makePlot(true) // zoombox - this *would* be 1D (dy=-1) but that's not allowed .then(doDrag('xy', 'nsew', 100, -1)) - .then(checkRanges({xaxis: [1, 2], yaxis: [1, 2], xaxis2: [0.5, 1.5], yaxis2: [0.5, 1.5]})) + .then(checkRanges({xaxis: [1, 2], yaxis: [1, 2], xaxis2: [0.5, 1.5], yaxis2: [0.5, 1.5]}, 'zoombox xy')) // first dblclick reverts to saved ranges .then(doDblClick('xy', 'nsew')) - .then(checkRanges()) + .then(checkRanges({}, 'dblclick xy')) // next dblclick autoscales ALL linked plots .then(doDblClick('xy', 'ns')) - .then(checkRanges({xaxis: autoRange, yaxis: autoRange, xaxis2: autoRange, yaxis2: autoRange})) + .then(checkRanges({xaxis: autoRange, yaxis: autoRange, xaxis2: autoRange, yaxis2: autoRange}, 'dblclick y')) // revert again .then(doDblClick('xy', 'nsew')) - .then(checkRanges()) + .then(checkRanges({}, 'dblclick xy #2')) // corner drag - full distance in one direction and no shift in the other gets averaged // into half distance in each .then(doDrag('xy', 'ne', -200, 0)) - .then(checkRanges({xaxis: [0, 4], yaxis: [0, 4], xaxis2: [-1, 3], yaxis2: [-1, 3]})) + .then(checkRanges({xaxis: [0, 4], yaxis: [0, 4], xaxis2: [-1, 3], yaxis2: [-1, 3]}, 'zoom xy ne')) // drag one end .then(doDrag('xy', 's', 53, -100)) - .then(checkRanges({xaxis: [-2, 6], yaxis: [-4, 4], xaxis2: [-3, 5], yaxis2: [-3, 5]})) + .then(checkRanges({xaxis: [-2, 6], yaxis: [-4, 4], xaxis2: [-3, 5], yaxis2: [-3, 5]}, 'zoom y s')) // middle of an axis .then(doDrag('xy', 'ew', -100, 53)) - .then(checkRanges({xaxis: [2, 10], yaxis: [-4, 4], xaxis2: [-3, 5], yaxis2: [-3, 5]})) + .then(checkRanges({xaxis: [2, 10], yaxis: [-4, 4], xaxis2: [-3, 5], yaxis2: [-3, 5]}, 'drag x ew')) // revert again .then(doDblClick('xy', 'nsew')) - .then(checkRanges()) + .then(checkRanges({}, 'dblclick xy #3')) // scroll wheel .then(function() { var mainDrag = getDragger('xy', 'nsew'); @@ -420,14 +427,15 @@ describe('axis zoom/pan and main plot zoom', function() { mouseEvent('scroll', mainDragCoords.x, mainDragCoords.y, {deltaY: 20, element: mainDrag}); }) .then(delay(constants.REDRAWDELAY + 10)) - .then(checkRanges({xaxis: [-0.4428, 2], yaxis: [0, 2.4428], xaxis2: [-0.2214, 2.2214], yaxis2: [-0.2214, 2.2214]})) + .then(checkRanges({xaxis: [-0.4428, 2], yaxis: [0, 2.4428], xaxis2: [-0.2214, 2.2214], yaxis2: [-0.2214, 2.2214]}, + 'scroll xy')) .then(function() { var ewDrag = getDragger('xy', 'ew'); var ewDragCoords = getNodeCoords(ewDrag); mouseEvent('scroll', ewDragCoords.x - 50, ewDragCoords.y, {deltaY: -20, element: ewDrag}); }) .then(delay(constants.REDRAWDELAY + 10)) - .then(checkRanges({xaxis: [-0.3321, 1.6679], yaxis: [0.2214, 2.2214]})) + .then(checkRanges({xaxis: [-0.3321, 1.6679], yaxis: [0.2214, 2.2214]}, 'scroll x')) .catch(failTest) .then(done); }); diff --git a/test/jasmine/tests/dragelement_test.js b/test/jasmine/tests/dragelement_test.js index 924f7f3bcaf..1b384f2a615 100644 --- a/test/jasmine/tests/dragelement_test.js +++ b/test/jasmine/tests/dragelement_test.js @@ -31,7 +31,7 @@ describe('dragElement', function() { afterEach(destroyGraphDiv); it('should init drag element', function() { - var options = { element: this.element }; + var options = { element: this.element, gd: this.gd }; dragElement.init(options); expect(this.element.style.pointerEvents).toEqual('all', 'with pointer event style'); @@ -42,6 +42,7 @@ describe('dragElement', function() { var args = []; var options = { element: this.element, + gd: this.gd, prepFn: function(event, startX, startY) { args = [event, startX, startY]; } @@ -60,6 +61,7 @@ describe('dragElement', function() { var args = []; var options = { element: this.element, + gd: this.gd, moveFn: function(dx, dy, dragged) { args = [dx, dy, dragged]; } @@ -79,6 +81,7 @@ describe('dragElement', function() { var args = []; var options = { element: this.element, + gd: this.gd, doneFn: function(dragged, numClicks) { args = [dragged, numClicks]; } @@ -104,7 +107,7 @@ describe('dragElement', function() { return d3.selectAll('.dragcover').size(); } - var options = { element: this.element }; + var options = { element: this.element, gd: this.gd }; dragElement.init(options); expect(countCoverSlip()).toEqual(0); @@ -120,7 +123,7 @@ describe('dragElement', function() { }); it('should fire off click event when down/up without dragging', function() { - var options = { element: this.element }; + var options = { element: this.element, gd: this.gd }; dragElement.init(options); var mockObj = {