diff --git a/src/components/legend/constants.js b/src/components/legend/constants.js new file mode 100644 index 00000000000..c36b7defb81 --- /dev/null +++ b/src/components/legend/constants.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = { + scrollBarWidth: 4, + scrollBarHeight: 20, + scrollBarColor: '#808BA4', + scrollBarMargin: 4 +}; diff --git a/src/components/legend/index.js b/src/components/legend/index.js index 464774b1d79..058f5c120b8 100644 --- a/src/components/legend/index.js +++ b/src/components/legend/index.js @@ -12,11 +12,20 @@ var Plotly = require('../../plotly'); var d3 = require('d3'); +var Lib = require('../../lib'); + +var Plots = require('../../plots/plots'); +var Fx = require('../../plots/cartesian/graph_interact'); + +var Color = require('../color'); +var Drawing = require('../drawing'); + var subTypes = require('../../traces/scatter/subtypes'); var styleOne = require('../../traces/pie/style_one'); var legend = module.exports = {}; +var constants = require('./constants'); legend.layoutAttributes = require('./attributes'); legend.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { @@ -33,10 +42,10 @@ legend.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { if(legendGetsTrace(trace)) { visibleTraces++; // always show the legend by default if there's a pie - if(Plotly.Plots.traceIs(trace, 'pie')) visibleTraces++; + if(Plots.traceIs(trace, 'pie')) visibleTraces++; } - if((Plotly.Plots.traceIs(trace, 'bar') && layoutOut.barmode==='stack') || + if((Plots.traceIs(trace, 'bar') && layoutOut.barmode==='stack') || ['tonextx','tonexty'].indexOf(trace.fill)!==-1) { defaultOrder = isGrouped({traceorder: defaultOrder}) ? 'grouped+reversed' : 'reversed'; @@ -49,19 +58,19 @@ legend.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { } function coerce(attr, dflt) { - return Plotly.Lib.coerce(containerIn, containerOut, + return Lib.coerce(containerIn, containerOut, legend.layoutAttributes, attr, dflt); } - var showLegend = Plotly.Lib.coerce(layoutIn, layoutOut, - Plotly.Plots.layoutAttributes, 'showlegend', visibleTraces > 1); + var showLegend = Lib.coerce(layoutIn, layoutOut, + Plots.layoutAttributes, 'showlegend', visibleTraces > 1); if(showLegend === false) return; coerce('bgcolor', layoutOut.paper_bgcolor); coerce('bordercolor'); coerce('borderwidth'); - Plotly.Lib.coerceFont(coerce, 'font', layoutOut.font); + Lib.coerceFont(coerce, 'font', layoutOut.font); coerce('traceorder', defaultOrder); if(isGrouped(layoutOut.legend)) coerce('tracegroupgap'); @@ -70,7 +79,7 @@ legend.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) { coerce('xanchor'); coerce('y'); coerce('yanchor'); - Plotly.Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); + Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); }; // ----------------------------------------------------- @@ -88,14 +97,14 @@ legend.lines = function(d){ fill.enter().append('path').classed('js-fill',true); fill.exit().remove(); fill.attr('d', 'M5,0h30v6h-30z') - .call(Plotly.Drawing.fillGroupStyle); + .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(Plotly.Drawing.lineGroupStyle); + line.call(Drawing.lineGroupStyle); }; legend.points = function(d){ @@ -111,7 +120,7 @@ legend.points = function(d){ // use d0.trace to infer arrayOk attributes function boundVal(attrIn, arrayToValFn, bounds) { - var valIn = Plotly.Lib.nestedProperty(trace, attrIn).get(), + var valIn = Lib.nestedProperty(trace, attrIn).get(), valToBound = (Array.isArray(valIn) && arrayToValFn) ? arrayToValFn(valIn) : valIn; @@ -131,10 +140,10 @@ legend.points = function(d){ if(showMarkers) { dEdit.mc = boundVal('marker.color', pickFirst); - dEdit.mo = boundVal('marker.opacity', Plotly.Lib.mean, [0.2, 1]); - dEdit.ms = boundVal('marker.size', Plotly.Lib.mean, [2, 16]); + 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', Plotly.Lib.mean, [0, 5]); + dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5]); tEdit.marker = { sizeref: 1, sizemin: 1, @@ -156,8 +165,8 @@ legend.points = function(d){ dEdit.tf = boundVal('textfont.family', pickFirst); } - dMod = [Plotly.Lib.minExtend(d0, dEdit)]; - tMod = Plotly.Lib.minExtend(trace, tEdit); + dMod = [Lib.minExtend(d0, dEdit)]; + tMod = Lib.minExtend(trace, tEdit); } var ptgroup = d3.select(this).select('g.legendpoints'); @@ -167,7 +176,7 @@ legend.points = function(d){ pts.enter().append('path').classed('scatterpts', true) .attr('transform', 'translate(20,0)'); pts.exit().remove(); - pts.call(Plotly.Drawing.pointStyle, tMod); + pts.call(Drawing.pointStyle, tMod); // 'mrc' is set in pointStyle and used in textPointStyle: // constrain it here @@ -179,7 +188,7 @@ legend.points = function(d){ .append('g').classed('pointtext',true) .append('text').attr('transform', 'translate(20,0)'); txt.exit().remove(); - txt.selectAll('text').call(Plotly.Drawing.textPointStyle, tMod); + txt.selectAll('text').call(Drawing.textPointStyle, tMod); }; @@ -189,7 +198,7 @@ legend.bars = function(d){ markerLine = marker.line||{}, barpath = d3.select(this).select('g.legendpoints') .selectAll('path.legendbar') - .data(Plotly.Plots.traceIs(trace, 'bar') ? [d] : []); + .data(Plots.traceIs(trace, 'bar') ? [d] : []); barpath.enter().append('path').classed('legendbar',true) .attr('d','M6,6H-6V-6H6Z') .attr('transform','translate(20,0)'); @@ -198,9 +207,9 @@ legend.bars = function(d){ var w = (d.mlw+1 || markerLine.width+1) - 1, p = d3.select(this); p.style('stroke-width',w+'px') - .call(Plotly.Color.fill, d.mc || marker.color); + .call(Color.fill, d.mc || marker.color); if(w) { - p.call(Plotly.Color.stroke, d.mlc || markerLine.color); + p.call(Color.stroke, d.mlc || markerLine.color); } }); }; @@ -209,7 +218,7 @@ legend.boxes = function(d){ var trace = d[0].trace, pts = d3.select(this).select('g.legendpoints') .selectAll('path.legendbox') - .data(Plotly.Plots.traceIs(trace, 'box') && trace.visible ? [d] : []); + .data(Plots.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') @@ -219,9 +228,9 @@ legend.boxes = function(d){ var w = (d.lw+1 || trace.line.width+1) - 1, p = d3.select(this); p.style('stroke-width', w+'px') - .call(Plotly.Color.fill, d.fc || trace.fillcolor); + .call(Color.fill, d.fc || trace.fillcolor); if(w) { - p.call(Plotly.Color.stroke, d.lc || trace.line.color); + p.call(Color.stroke, d.lc || trace.line.color); } }); }; @@ -230,7 +239,7 @@ legend.pie = function(d) { var trace = d[0].trace, pts = d3.select(this).select('g.legendpoints') .selectAll('path.legendpie') - .data(Plotly.Plots.traceIs(trace, 'pie') && trace.visible ? [d] : []); + .data(Plots.traceIs(trace, 'pie') && trace.visible ? [d] : []); pts.enter().append('path').classed('legendpie', true) .attr('d', 'M6,6H-6V-6H6Z') .attr('transform', 'translate(20,0)'); @@ -277,7 +286,7 @@ legend.style = function(s) { legend.texts = function(context, td, d, i, traces){ var fullLayout = td._fullLayout, trace = d[0].trace, - isPie = Plotly.Plots.traceIs(trace, 'pie'), + isPie = Plots.traceIs(trace, 'pie'), traceIndex = trace.index, name = isPie ? d[0].label : trace.name; @@ -286,12 +295,18 @@ legend.texts = function(context, td, d, i, traces){ text.enter().append('text').classed('legendtext', true); text.attr({ x: 40, - y: 0 + y: 0, + 'data-unformatted': name }) - .style('text-anchor', 'start') - .call(Plotly.Drawing.font, fullLayout.legend.font) - .text(name) - .attr({'data-unformatted': name}); + .style({ + 'text-anchor': 'start', + '-webkit-user-select': 'none', + '-moz-user-select': 'none', + '-ms-user-select': 'none', + 'user-select': 'none' + }) + .call(Drawing.font, fullLayout.legend.font) + .text(name); function textLayout(s){ Plotly.util.convertToTspans(s, function(){ @@ -319,7 +334,7 @@ legend.texts = function(context, td, d, i, traces){ // ----------------------------------------------------- function legendGetsTrace(trace) { - return trace.visible && Plotly.Plots.traceIs(trace, 'showLegend'); + return trace.visible && Plots.traceIs(trace, 'showLegend'); } function isGrouped(legendLayout) { @@ -365,7 +380,7 @@ legend.getLegendData = function(calcdata, opts) { if(!legendGetsTrace(trace) || !trace.showlegend) continue; - if(Plotly.Plots.traceIs(trace, 'pie')) { + if(Plots.traceIs(trace, 'pie')) { if(!slicesShown[lgroup]) slicesShown[lgroup] = {}; for(j = 0; j < cd.length; j++) { labelj = cd[j].label; @@ -426,7 +441,7 @@ legend.draw = function(td) { if(!fullLayout.showlegend || !legendData.length) { fullLayout._infolayer.selectAll('.legend').remove(); - Plotly.Plots.autoMargin(td, 'legend'); + Plots.autoMargin(td, 'legend'); return; } @@ -435,22 +450,43 @@ legend.draw = function(td) { var legendsvg = fullLayout._infolayer.selectAll('svg.legend') .data([0]); - legendsvg.enter(0).append('svg') - .attr('class','legend'); + legendsvg.enter().append('svg') + .attr({ + 'class': 'legend', + 'pointer-events': 'all' + }); - var bgRect = legendsvg.selectAll('rect.bg') + var bg = legendsvg.selectAll('rect.bg') .data([0]); - bgRect.enter(0).append('rect') - .attr('class','bg'); - bgRect - .call(Plotly.Color.stroke, opts.bordercolor) - .call(Plotly.Color.fill, opts.bgcolor) - .style('stroke-width', opts.borderwidth+'px'); - - var groups = legendsvg.selectAll('g.groups') - .data(legendData); + bg.enter().append('rect') + .attr({ + 'class': 'bg', + 'shape-rendering': 'crispEdges' + }) + .call(Color.stroke, opts.bordercolor) + .call(Color.fill, opts.bgcolor) + .style('stroke-width', opts.borderwidth + 'px'); + + var scrollBox = legendsvg.selectAll('g.scrollbox') + .data([0]); + scrollBox.enter().append('g') + .attr('class', 'scrollbox'); + scrollBox.exit().remove(); + + var scrollBar = legendsvg.selectAll('rect.scrollbar') + .data([0]); + scrollBar.enter().append('rect') + .attr({ + 'class': 'scrollbar', + 'rx': 20, + 'ry': 2 + }) + .call(Color.fill, '#808BA4'); - groups.enter().append('g').attr('class', 'groups'); + var groups = scrollBox.selectAll('g.groups') + .data(legendData); + groups.enter().append('g') + .attr('class', 'groups'); groups.exit().remove(); if(isGrouped(opts)) { @@ -460,7 +496,7 @@ legend.draw = function(td) { } var traces = groups.selectAll('g.traces') - .data(Plotly.Lib.identity); + .data(Lib.identity); traces.enter().append('g').attr('class', 'traces'); traces.exit().remove(); @@ -468,7 +504,7 @@ legend.draw = function(td) { traces.call(legend.style) .style('opacity', function(d) { var trace = d[0].trace; - if(Plotly.Plots.traceIs(trace, 'pie')) { + if(Plots.traceIs(trace, 'pie')) { return hiddenSlices.indexOf(d[0].label) !== -1 ? 0.5 : 1; } else { return trace.visible === 'legendonly' ? 0.5 : 1; @@ -483,7 +519,7 @@ legend.draw = function(td) { .classed('legendtoggle', true) .style('cursor', 'pointer') .attr('pointer-events', 'all') - .call(Plotly.Color.fill, 'rgba(0,0,0,0)'); + .call(Color.fill, 'rgba(0,0,0,0)'); traceToggle.on('click', function() { if(td._dragged) return; @@ -494,7 +530,7 @@ legend.draw = function(td) { tracei, newVisible; - if(Plotly.Plots.traceIs(trace, 'pie')) { + if(Plots.traceIs(trace, 'pie')) { var thisLabel = d[0].label, newHiddenSlices = hiddenSlices.slice(), thisLabelIndex = newHiddenSlices.indexOf(thisLabel); @@ -521,8 +557,101 @@ legend.draw = function(td) { }); }); + // Position and size the legend legend.repositionLegend(td, traces); + // Scroll section must be executed after repositionLegend. + // It requires the legend width, height, x and y to position the scrollbox + // and these values are mutated in repositionLegend. + var gs = fullLayout._size, + lx = gs.l + gs.w * opts.x, + ly = gs.t + gs.h * (1-opts.y); + + if(opts.xanchor === 'right' || (opts.xanchor === 'auto' && opts.x >= 2 / 3)) { + lx -= opts.width; + } + else if(opts.xanchor === 'center' || (opts.xanchor === 'auto' && opts.x > 1 / 3)) { + lx -= opts.width / 2; + } + + if(opts.yanchor === 'bottom' || (opts.yanchor === 'auto' && opts.y <= 1 / 3)) { + ly -= opts.height; + } + else if(opts.yanchor === 'middle' || (opts.yanchor === 'auto' && opts.y < 2 / 3)) { + ly -= opts.height / 2; + } + + // Deal with scrolling + var plotHeight = fullLayout.height - fullLayout.margin.b, + scrollheight = Math.min(plotHeight - ly, opts.height), + scrollPosition = scrollBox.attr('data-scroll') ? scrollBox.attr('data-scroll') : 0; + + scrollBox.attr('transform', 'translate(0, ' + scrollPosition + ')'); + bg.attr({ + width: opts.width - 2 * opts.borderwidth, + height: scrollheight - 2 * opts.borderwidth, + x: opts.borderwidth, + y: opts.borderwidth + }); + + legendsvg.call(Drawing.setRect, lx, ly, opts.width, scrollheight); + + // If scrollbar should be shown. + if(td.firstRender && opts.height - scrollheight > 0 && !td._context.staticPlot){ + + bg.attr({ width: opts.width - 2 * opts.borderwidth + constants.scrollBarWidth }); + + legendsvg.node().addEventListener('wheel', function(e){ + e.preventDefault(); + scrollHandler(e.deltaY / 20); + }); + + scrollBar.node().addEventListener('mousedown', function(e) { + e.preventDefault(); + + function mMove(e) { + if(e.buttons === 1){ + scrollHandler(e.movementY); + } + } + + function mUp() { + scrollBar.node().removeEventListener('mousemove', mMove); + window.removeEventListener('mouseup', mUp); + } + + window.addEventListener('mousemove', mMove); + window.addEventListener('mouseup', mUp); + }); + + // Move scrollbar to starting position on the first render + scrollBar.call( + Drawing.setRect, + opts.width - (constants.scrollBarWidth + constants.scrollBarMargin), + constants.scrollBarMargin, + constants.scrollBarWidth, + constants.scrollBarHeight + ); + } + + function scrollHandler(delta){ + + var scrollBarTrack = scrollheight - constants.scrollBarHeight - 2 * constants.scrollBarMargin, + translateY = scrollBox.attr('data-scroll'), + scrollBoxY = Lib.constrain(translateY - delta, Math.min(scrollheight - opts.height, 0), 0), + scrollBarY = -scrollBoxY / (opts.height - scrollheight) * scrollBarTrack + constants.scrollBarMargin; + + scrollBox.attr('data-scroll', scrollBoxY); + scrollBox.attr('transform', 'translate(0, ' + scrollBoxY + ')'); + scrollBar.call( + Drawing.setRect, + opts.width - (constants.scrollBarWidth + constants.scrollBarMargin), + scrollBarY, + constants.scrollBarWidth, + constants.scrollBarHeight + ); + } + if(td._context.editable) { var xf, yf, @@ -531,32 +660,32 @@ legend.draw = function(td) { lw, lh; - Plotly.Fx.dragElement({ + Fx.dragElement({ element: legendsvg.node(), prepFn: function() { x0 = Number(legendsvg.attr('x')); y0 = Number(legendsvg.attr('y')); lw = Number(legendsvg.attr('width')); lh = Number(legendsvg.attr('height')); - Plotly.Fx.setCursor(legendsvg); + Fx.setCursor(legendsvg); }, moveFn: function(dx, dy) { var gs = td._fullLayout._size; - legendsvg.call(Plotly.Drawing.setPosition, x0+dx, y0+dy); + legendsvg.call(Drawing.setPosition, x0+dx, y0+dy); - xf = Plotly.Fx.dragAlign(x0+dx, lw, gs.l, gs.l+gs.w, + xf = Fx.dragAlign(x0+dx, lw, gs.l, gs.l+gs.w, opts.xanchor); - yf = Plotly.Fx.dragAlign(y0+dy+lh, -lh, gs.t+gs.h, gs.t, + yf = Fx.dragAlign(y0+dy+lh, -lh, gs.t+gs.h, gs.t, opts.yanchor); - var csr = Plotly.Fx.dragCursors(xf, yf, + var csr = Fx.dragCursors(xf, yf, opts.xanchor, opts.yanchor); - Plotly.Fx.setCursor(legendsvg, csr); + Fx.setCursor(legendsvg, csr); }, doneFn: function(dragged) { - Plotly.Fx.setCursor(legendsvg); - if(dragged && xf!==undefined && yf!==undefined) { + Fx.setCursor(legendsvg); + if(dragged && xf !== undefined && yf !== undefined) { Plotly.relayout(td, {'legend.x': xf, 'legend.y': yf}); } } @@ -568,12 +697,10 @@ legend.repositionLegend = function(td, traces){ var fullLayout = td._fullLayout, gs = fullLayout._size, opts = fullLayout.legend, - borderwidth = opts.borderwidth, + borderwidth = opts.borderwidth; - // add the legend elements, keeping track of the - // legend size (in px) as we go - legendwidth = 0, - legendheight = 0; + opts.width = 0, + opts.height = 0, traces.each(function(d){ var trace = d[0].trace, @@ -582,8 +709,8 @@ legend.repositionLegend = function(td, traces){ text = g.selectAll('.legendtext'), tspans = g.selectAll('.legendtext>tspan'), tHeight = opts.font.size * 1.3, - tLines = tspans[0].length||1, - tWidth = text.node() && Plotly.Drawing.bBox(text.node()).width, + tLines = tspans[0].length || 1, + tWidth = text.node() && Drawing.bBox(text.node()).width, mathjaxGroup = g.select('g[class*=math-group]'), textY, tHeightFull; @@ -594,15 +721,15 @@ legend.repositionLegend = function(td, traces){ } if(mathjaxGroup.node()) { - var mathjaxBB = Plotly.Drawing.bBox(mathjaxGroup.node()); + var mathjaxBB = Drawing.bBox(mathjaxGroup.node()); tHeight = mathjaxBB.height; tWidth = mathjaxBB.width; - mathjaxGroup.attr('transform','translate(0,'+(tHeight/4)+')'); + mathjaxGroup.attr('transform','translate(0,' + (tHeight / 4) + ')'); } else { // approximation to height offset to center the font // to avoid getBoundingClientRect - textY = tHeight * (0.3 + (1-tLines)/2); + textY = tHeight * (0.3 + (1-tLines) / 2); text.attr('y',textY); tspans.attr('y',textY); } @@ -611,22 +738,23 @@ legend.repositionLegend = function(td, traces){ g.attr('transform', 'translate(' + borderwidth + ',' + - (5 + borderwidth + legendheight + tHeightFull/2) + + (5 + borderwidth + opts.height + tHeightFull / 2) + ')' ); bg.attr({x: 0, y: -tHeightFull / 2, height: tHeightFull}); - legendheight += tHeightFull; - legendwidth = Math.max(legendwidth, tWidth||0); + opts.height += tHeightFull; + opts.width = Math.max(opts.width, tWidth || 0); }); - if(isGrouped(opts)) legendheight += (opts._lgroupsLength-1) * opts.tracegroupgap; - traces.selectAll('.legendtoggle') - .attr('width', (td._context.editable ? 0 : legendwidth) + 40); + opts.width += 45 + borderwidth * 2; + opts.height += 10 + borderwidth * 2; - legendwidth += 45+borderwidth*2; - legendheight += 10+borderwidth*2; + if(isGrouped(opts)) opts.height += (opts._lgroupsLength-1) * opts.tracegroupgap; + + traces.selectAll('.legendtoggle') + .attr('width', (td._context.editable ? 0 : opts.width) + 40); // now position the legend. for both x,y the positions are recorded as // fractions of the plot area (left, bottom = 0,0). Outside the plot @@ -634,48 +762,42 @@ legend.repositionLegend = function(td, traces){ // values <1/3 align the low side at that fraction, 1/3-2/3 align the // center at that fraction, >2/3 align the right at that fraction - var lx = gs.l+gs.w*opts.x, - ly = gs.t+gs.h*(1-opts.y); + var lx = gs.l + gs.w * opts.x, + ly = gs.t + gs.h * (1-opts.y); var xanchor = 'left'; - if(opts.xanchor==='right' || (opts.xanchor==='auto' && opts.x>=2/3)) { - lx -= legendwidth; + if(opts.xanchor === 'right' || (opts.xanchor === 'auto' && opts.x >= 2 / 3)) { + lx -= opts.width; xanchor = 'right'; } - else if(opts.xanchor==='center' || (opts.xanchor==='auto' && opts.x>1/3)) { - lx -= legendwidth/2; + else if(opts.xanchor === 'center' || (opts.xanchor === 'auto' && opts.x > 1 / 3)) { + lx -= opts.width / 2; xanchor = 'center'; } var yanchor = 'top'; - if(opts.yanchor==='bottom' || (opts.yanchor==='auto' && opts.y<=1/3)) { - ly -= legendheight; + if(opts.yanchor === 'bottom' || (opts.yanchor === 'auto' && opts.y <= 1 / 3)) { + ly -= opts.height; yanchor = 'bottom'; } - else if(opts.yanchor==='middle' || (opts.yanchor==='auto' && opts.y<2/3)) { - ly -= legendheight/2; + else if(opts.yanchor === 'middle' || (opts.yanchor === 'auto' && opts.y < 2 / 3)) { + ly -= opts.height / 2; yanchor = 'middle'; } // make sure we're only getting full pixels - legendwidth = Math.ceil(legendwidth); - legendheight = Math.ceil(legendheight); + opts.width = Math.ceil(opts.width); + opts.height = Math.ceil(opts.height); lx = Math.round(lx); ly = Math.round(ly); - fullLayout._infolayer.selectAll('svg.legend') - .call(Plotly.Drawing.setRect, lx, ly, legendwidth, legendheight); - fullLayout._infolayer.selectAll('svg.legend .bg') - .call(Plotly.Drawing.setRect, borderwidth/2, borderwidth/2, - legendwidth-borderwidth, legendheight-borderwidth); - // lastly check if the margin auto-expand has changed - Plotly.Plots.autoMargin(td,'legend',{ + Plots.autoMargin(td,'legend',{ x: opts.x, y: opts.y, - l: legendwidth * ({right:1, center:0.5}[xanchor]||0), - r: legendwidth * ({left:1, center:0.5}[xanchor]||0), - b: legendheight * ({top:1, middle:0.5}[yanchor]||0), - t: legendheight * ({bottom:1, middle:0.5}[yanchor]||0) + l: opts.width * ({right:1, center:0.5}[xanchor] || 0), + r: opts.width * ({left:1, center:0.5}[xanchor] || 0), + b: opts.height * ({top:1, middle:0.5}[yanchor] || 0), + t: opts.height * ({bottom:1, middle:0.5}[yanchor] || 0) }); }; diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index 032b01fee14..d8b246d7318 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -80,7 +80,6 @@ util.convertToTspans = function(_context, _callback){ parent.selectAll('svg.' + svgClass).remove(); parent.selectAll('g.' + svgClass + '-group').remove(); _context.style({visibility: null}); - // for Plotly.Drawing.bBox: unlink text and all parents from its cached box for(var up = _context.node(); up && up.removeAttribute; up = up.parentNode) { up.removeAttribute('data-bb'); } diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index d37e78635bd..f1ab41db719 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -292,13 +292,24 @@ Plotly.plot = function(gd, data, layout, config) { // source links Plots.addLinks(gd); + // Mark the first render as complete + gd._replotting = false; + return Plots.previousPromises(gd); } + // An initial paint must be completed before these components can be + // correctly sized and the whole plot re-margined. gd._replotting must + // be set to false before these will work properly. + function finalDraw(){ + Shapes.drawAll(gd); + Plotly.Annotations.drawAll(gd); + Legend.draw(gd); + } + function cleanUp() { // now we're REALLY TRULY done plotting... // so mark it as done and let other procedures call a replot - gd._replotting = false; Lib.markTime('done plot'); gd.emit('plotly_afterplot'); } @@ -310,7 +321,8 @@ Plotly.plot = function(gd, data, layout, config) { marginPushersAgain, positionAndAutorange, drawAxes, - drawData + drawData, + finalDraw ], gd, cleanUp); // even if everything we did was synchronous, return a promise diff --git a/src/snapshot/index.js b/src/snapshot/index.js index fcf209fb5d3..2b662846928 100644 --- a/src/snapshot/index.js +++ b/src/snapshot/index.js @@ -9,8 +9,6 @@ 'use strict'; -var Plotly = require('../plotly'); - function getDelay(fullLayout) { return (fullLayout._hasGL3D || fullLayout._hasGL2D) ? 500 : 0; } @@ -24,8 +22,6 @@ function getRedrawFunc(gd) { (gd.data && gd.data[0] && gd.data[0].r) ) return; - Plotly.Annotations.drawAll(gd); - Plotly.Legend.draw(gd, fullLayout.showlegend); (gd.calcdata || []).forEach(function(d) { if(d[0] && d[0].t && d[0].t.cb) d[0].t.cb(); }); diff --git a/test/image/baselines/0.png b/test/image/baselines/0.png index 5133fb33e13..5a839d4ac52 100644 Binary files a/test/image/baselines/0.png and b/test/image/baselines/0.png differ diff --git a/test/image/baselines/11.png b/test/image/baselines/11.png index ed50cb35c92..998509dd8d5 100644 Binary files a/test/image/baselines/11.png and b/test/image/baselines/11.png differ diff --git a/test/image/baselines/15.png b/test/image/baselines/15.png index 277d718a46d..d3e08275fce 100644 Binary files a/test/image/baselines/15.png and b/test/image/baselines/15.png differ diff --git a/test/image/baselines/17.png b/test/image/baselines/17.png index 1e098f1a02c..98e19ab95ac 100644 Binary files a/test/image/baselines/17.png and b/test/image/baselines/17.png differ diff --git a/test/image/baselines/19.png b/test/image/baselines/19.png index 57bb753c15f..3d0619e7d0c 100644 Binary files a/test/image/baselines/19.png and b/test/image/baselines/19.png differ diff --git a/test/image/baselines/20.png b/test/image/baselines/20.png index ebc003d849f..5c372badb0c 100644 Binary files a/test/image/baselines/20.png and b/test/image/baselines/20.png differ diff --git a/test/image/baselines/23.png b/test/image/baselines/23.png index 7d0c1c793bb..cf24f84cb93 100644 Binary files a/test/image/baselines/23.png and b/test/image/baselines/23.png differ diff --git a/test/image/baselines/24.png b/test/image/baselines/24.png index 532d9b6a055..1609cfe305b 100644 Binary files a/test/image/baselines/24.png and b/test/image/baselines/24.png differ diff --git a/test/image/baselines/27.png b/test/image/baselines/27.png index 7eb41cc8ac9..be45068e67d 100644 Binary files a/test/image/baselines/27.png and b/test/image/baselines/27.png differ diff --git a/test/image/baselines/28.png b/test/image/baselines/28.png index 594cce0b7bd..bcd2b96b1f1 100644 Binary files a/test/image/baselines/28.png and b/test/image/baselines/28.png differ diff --git a/test/image/baselines/29.png b/test/image/baselines/29.png index a90c21934ba..a5bed7f5221 100644 Binary files a/test/image/baselines/29.png and b/test/image/baselines/29.png differ diff --git a/test/image/baselines/5.png b/test/image/baselines/5.png index fa5d18564fa..e0538488ab2 100644 Binary files a/test/image/baselines/5.png and b/test/image/baselines/5.png differ diff --git a/test/image/baselines/autorange-tozero-rangemode.png b/test/image/baselines/autorange-tozero-rangemode.png index cb57b380158..7c4d3d477e0 100644 Binary files a/test/image/baselines/autorange-tozero-rangemode.png and b/test/image/baselines/autorange-tozero-rangemode.png differ diff --git a/test/image/baselines/axes-autotype-empty.png b/test/image/baselines/axes-autotype-empty.png index 803acf873c2..55d965bd247 100644 Binary files a/test/image/baselines/axes-autotype-empty.png and b/test/image/baselines/axes-autotype-empty.png differ diff --git a/test/image/baselines/axes-ticks.png b/test/image/baselines/axes-ticks.png index 2f53bf1ab3e..ac908438243 100644 Binary files a/test/image/baselines/axes-ticks.png and b/test/image/baselines/axes-ticks.png differ diff --git a/test/image/baselines/axes_booleans.png b/test/image/baselines/axes_booleans.png index b4969f1ab8e..01cfaaa0db7 100644 Binary files a/test/image/baselines/axes_booleans.png and b/test/image/baselines/axes_booleans.png differ diff --git a/test/image/baselines/axes_labels.png b/test/image/baselines/axes_labels.png index e0d5d95c920..58917494cb1 100644 Binary files a/test/image/baselines/axes_labels.png and b/test/image/baselines/axes_labels.png differ diff --git a/test/image/baselines/axes_lines.png b/test/image/baselines/axes_lines.png index 75302915254..ac009453593 100644 Binary files a/test/image/baselines/axes_lines.png and b/test/image/baselines/axes_lines.png differ diff --git a/test/image/baselines/axes_range_manual.png b/test/image/baselines/axes_range_manual.png index a53988fba67..60540ad468b 100644 Binary files a/test/image/baselines/axes_range_manual.png and b/test/image/baselines/axes_range_manual.png differ diff --git a/test/image/baselines/axes_range_type.png b/test/image/baselines/axes_range_type.png index 7f15a3325a4..af39babcf82 100644 Binary files a/test/image/baselines/axes_range_type.png and b/test/image/baselines/axes_range_type.png differ diff --git a/test/image/baselines/bar_group_percent.png b/test/image/baselines/bar_group_percent.png index 45e909bbb0a..c0ccc7f9c13 100644 Binary files a/test/image/baselines/bar_group_percent.png and b/test/image/baselines/bar_group_percent.png differ diff --git a/test/image/baselines/bar_line.png b/test/image/baselines/bar_line.png index 3d880ccccbf..5d922d1ba0a 100644 Binary files a/test/image/baselines/bar_line.png and b/test/image/baselines/bar_line.png differ diff --git a/test/image/baselines/bar_stackto1.png b/test/image/baselines/bar_stackto1.png index a2a0b38089c..c4ca8cddfa9 100644 Binary files a/test/image/baselines/bar_stackto1.png and b/test/image/baselines/bar_stackto1.png differ diff --git a/test/image/baselines/bar_stackto100_negative.png b/test/image/baselines/bar_stackto100_negative.png index 86300d8c214..17fb79bd290 100644 Binary files a/test/image/baselines/bar_stackto100_negative.png and b/test/image/baselines/bar_stackto100_negative.png differ diff --git a/test/image/baselines/basic_area.png b/test/image/baselines/basic_area.png index 3707ed0b84f..3cc95b67fbb 100644 Binary files a/test/image/baselines/basic_area.png and b/test/image/baselines/basic_area.png differ diff --git a/test/image/baselines/basic_line.png b/test/image/baselines/basic_line.png index 186174010cb..70c712c3b3e 100644 Binary files a/test/image/baselines/basic_line.png and b/test/image/baselines/basic_line.png differ diff --git a/test/image/baselines/benchmarks.png b/test/image/baselines/benchmarks.png index 4d4cd85983f..d28ade61946 100644 Binary files a/test/image/baselines/benchmarks.png and b/test/image/baselines/benchmarks.png differ diff --git a/test/image/baselines/box_grouped.png b/test/image/baselines/box_grouped.png index 25a088e51b6..291bc99a09a 100644 Binary files a/test/image/baselines/box_grouped.png and b/test/image/baselines/box_grouped.png differ diff --git a/test/image/baselines/box_grouped_horz.png b/test/image/baselines/box_grouped_horz.png index 64c959bfbe2..b2f67768d6b 100644 Binary files a/test/image/baselines/box_grouped_horz.png and b/test/image/baselines/box_grouped_horz.png differ diff --git a/test/image/baselines/bubble_markersize0.png b/test/image/baselines/bubble_markersize0.png index 5d88235242f..3423fa0c21d 100644 Binary files a/test/image/baselines/bubble_markersize0.png and b/test/image/baselines/bubble_markersize0.png differ diff --git a/test/image/baselines/bubble_nonnumeric-sizes.png b/test/image/baselines/bubble_nonnumeric-sizes.png index 7c15676b2c5..e586fa1fd2c 100644 Binary files a/test/image/baselines/bubble_nonnumeric-sizes.png and b/test/image/baselines/bubble_nonnumeric-sizes.png differ diff --git a/test/image/baselines/custom_size_subplot.png b/test/image/baselines/custom_size_subplot.png index 6e080840bd1..e8fcddc8aef 100644 Binary files a/test/image/baselines/custom_size_subplot.png and b/test/image/baselines/custom_size_subplot.png differ diff --git a/test/image/baselines/error_bar_bar.png b/test/image/baselines/error_bar_bar.png index 5fb87aca20d..3ac2a1ebb2d 100644 Binary files a/test/image/baselines/error_bar_bar.png and b/test/image/baselines/error_bar_bar.png differ diff --git a/test/image/baselines/error_bar_style.png b/test/image/baselines/error_bar_style.png index a93648a3237..28f5d931c2f 100644 Binary files a/test/image/baselines/error_bar_style.png and b/test/image/baselines/error_bar_style.png differ diff --git a/test/image/baselines/geo_africa-insets.png b/test/image/baselines/geo_africa-insets.png index b23c20e778e..8af3269d1d5 100644 Binary files a/test/image/baselines/geo_africa-insets.png and b/test/image/baselines/geo_africa-insets.png differ diff --git a/test/image/baselines/geo_bg-color.png b/test/image/baselines/geo_bg-color.png index 016984c72d7..177b51ccddd 100644 Binary files a/test/image/baselines/geo_bg-color.png and b/test/image/baselines/geo_bg-color.png differ diff --git a/test/image/baselines/geo_bubbles-colorscales.png b/test/image/baselines/geo_bubbles-colorscales.png index 5c2c251addd..57c73a839bc 100644 Binary files a/test/image/baselines/geo_bubbles-colorscales.png and b/test/image/baselines/geo_bubbles-colorscales.png differ diff --git a/test/image/baselines/geo_bubbles-sizeref.png b/test/image/baselines/geo_bubbles-sizeref.png index 43d02b1f095..8a98a5b2f03 100644 Binary files a/test/image/baselines/geo_bubbles-sizeref.png and b/test/image/baselines/geo_bubbles-sizeref.png differ diff --git a/test/image/baselines/geo_legendonly.png b/test/image/baselines/geo_legendonly.png index caa3a04d6f2..0d74a4a40ba 100644 Binary files a/test/image/baselines/geo_legendonly.png and b/test/image/baselines/geo_legendonly.png differ diff --git a/test/image/baselines/gl3d_log-axis-big.png b/test/image/baselines/gl3d_log-axis-big.png index 7c65fdbbd4b..7ebf8f9bac6 100644 Binary files a/test/image/baselines/gl3d_log-axis-big.png and b/test/image/baselines/gl3d_log-axis-big.png differ diff --git a/test/image/baselines/gl3d_log-axis.png b/test/image/baselines/gl3d_log-axis.png index c2230fb006e..320c3ef1234 100644 Binary files a/test/image/baselines/gl3d_log-axis.png and b/test/image/baselines/gl3d_log-axis.png differ diff --git a/test/image/baselines/gl3d_multi-scene.png b/test/image/baselines/gl3d_multi-scene.png index 889fb3939f2..60c16a28cfb 100644 Binary files a/test/image/baselines/gl3d_multi-scene.png and b/test/image/baselines/gl3d_multi-scene.png differ diff --git a/test/image/baselines/gl3d_opacity-scaling-spikes.png b/test/image/baselines/gl3d_opacity-scaling-spikes.png index 6f87d3439b8..433d49e74e1 100644 Binary files a/test/image/baselines/gl3d_opacity-scaling-spikes.png and b/test/image/baselines/gl3d_opacity-scaling-spikes.png differ diff --git a/test/image/baselines/gl3d_projection-traces.png b/test/image/baselines/gl3d_projection-traces.png index a6057638200..49470a45b25 100644 Binary files a/test/image/baselines/gl3d_projection-traces.png and b/test/image/baselines/gl3d_projection-traces.png differ diff --git a/test/image/baselines/grouped_bar.png b/test/image/baselines/grouped_bar.png index 97ae2ffeff4..bdc4cbb1c10 100644 Binary files a/test/image/baselines/grouped_bar.png and b/test/image/baselines/grouped_bar.png differ diff --git a/test/image/baselines/hist_grouped.png b/test/image/baselines/hist_grouped.png index 4336d4852fd..9ecbaaf117d 100644 Binary files a/test/image/baselines/hist_grouped.png and b/test/image/baselines/hist_grouped.png differ diff --git a/test/image/baselines/hist_stacked.png b/test/image/baselines/hist_stacked.png index ff9f3640b36..a7763b8926f 100644 Binary files a/test/image/baselines/hist_stacked.png and b/test/image/baselines/hist_stacked.png differ diff --git a/test/image/baselines/legend_inside.png b/test/image/baselines/legend_inside.png index 85ba1630455..43e346b98ef 100644 Binary files a/test/image/baselines/legend_inside.png and b/test/image/baselines/legend_inside.png differ diff --git a/test/image/baselines/legend_labels.png b/test/image/baselines/legend_labels.png index cf7e9d9622a..6b9cbd995e7 100644 Binary files a/test/image/baselines/legend_labels.png and b/test/image/baselines/legend_labels.png differ diff --git a/test/image/baselines/legend_scroll.png b/test/image/baselines/legend_scroll.png new file mode 100644 index 00000000000..b26cd5db0bc Binary files /dev/null and b/test/image/baselines/legend_scroll.png differ diff --git a/test/image/baselines/legend_style.png b/test/image/baselines/legend_style.png index 8e6ba7e52fa..2df126e26ce 100644 Binary files a/test/image/baselines/legend_style.png and b/test/image/baselines/legend_style.png differ diff --git a/test/image/baselines/legend_visibility.png b/test/image/baselines/legend_visibility.png index 77624f77710..860bc716f06 100644 Binary files a/test/image/baselines/legend_visibility.png and b/test/image/baselines/legend_visibility.png differ diff --git a/test/image/baselines/legendgroup.png b/test/image/baselines/legendgroup.png index 97a2f63c932..9ebdd53fa54 100644 Binary files a/test/image/baselines/legendgroup.png and b/test/image/baselines/legendgroup.png differ diff --git a/test/image/baselines/legendgroup_bar-stack.png b/test/image/baselines/legendgroup_bar-stack.png index 2d1012b4700..1c205b1d7b4 100644 Binary files a/test/image/baselines/legendgroup_bar-stack.png and b/test/image/baselines/legendgroup_bar-stack.png differ diff --git a/test/image/baselines/line_scatter.png b/test/image/baselines/line_scatter.png index c981f8d8bbf..1791dccb1e7 100644 Binary files a/test/image/baselines/line_scatter.png and b/test/image/baselines/line_scatter.png differ diff --git a/test/image/baselines/line_style.png b/test/image/baselines/line_style.png index 0b54d003cd3..5b7949b5d05 100644 Binary files a/test/image/baselines/line_style.png and b/test/image/baselines/line_style.png differ diff --git a/test/image/baselines/multiple_axes_double.png b/test/image/baselines/multiple_axes_double.png index 135477b5cc7..9185bb90df9 100644 Binary files a/test/image/baselines/multiple_axes_double.png and b/test/image/baselines/multiple_axes_double.png differ diff --git a/test/image/baselines/multiple_axes_multiple.png b/test/image/baselines/multiple_axes_multiple.png index b48e0804996..7bdf1630883 100644 Binary files a/test/image/baselines/multiple_axes_multiple.png and b/test/image/baselines/multiple_axes_multiple.png differ diff --git a/test/image/baselines/multiple_subplots.png b/test/image/baselines/multiple_subplots.png index 1920139c013..465f7c634b0 100644 Binary files a/test/image/baselines/multiple_subplots.png and b/test/image/baselines/multiple_subplots.png differ diff --git a/test/image/baselines/pie_fonts.png b/test/image/baselines/pie_fonts.png index 618227f31fa..1a58140d2f7 100644 Binary files a/test/image/baselines/pie_fonts.png and b/test/image/baselines/pie_fonts.png differ diff --git a/test/image/baselines/pie_label0_dlabel.png b/test/image/baselines/pie_label0_dlabel.png index 0feef556c38..c894b985d75 100644 Binary files a/test/image/baselines/pie_label0_dlabel.png and b/test/image/baselines/pie_label0_dlabel.png differ diff --git a/test/image/baselines/pie_scale_textpos_hideslices.png b/test/image/baselines/pie_scale_textpos_hideslices.png index 83449ecc3a0..72e8241f847 100644 Binary files a/test/image/baselines/pie_scale_textpos_hideslices.png and b/test/image/baselines/pie_scale_textpos_hideslices.png differ diff --git a/test/image/baselines/pie_simple.png b/test/image/baselines/pie_simple.png index 7297df72315..516515244a7 100644 Binary files a/test/image/baselines/pie_simple.png and b/test/image/baselines/pie_simple.png differ diff --git a/test/image/baselines/plot_types.png b/test/image/baselines/plot_types.png index a89b8ea6770..dfe2fe6d4f3 100644 Binary files a/test/image/baselines/plot_types.png and b/test/image/baselines/plot_types.png differ diff --git a/test/image/baselines/pseudo_html.png b/test/image/baselines/pseudo_html.png index f92ebd40ba5..d15e9753126 100644 Binary files a/test/image/baselines/pseudo_html.png and b/test/image/baselines/pseudo_html.png differ diff --git a/test/image/baselines/scatter_fill_no_opacity.png b/test/image/baselines/scatter_fill_no_opacity.png index ecfe7043e0b..4a6d73cd45a 100644 Binary files a/test/image/baselines/scatter_fill_no_opacity.png and b/test/image/baselines/scatter_fill_no_opacity.png differ diff --git a/test/image/baselines/shared_axes_subplots.png b/test/image/baselines/shared_axes_subplots.png index ee80a69385a..3a4e3dddf6f 100644 Binary files a/test/image/baselines/shared_axes_subplots.png and b/test/image/baselines/shared_axes_subplots.png differ diff --git a/test/image/baselines/show_legend.png b/test/image/baselines/show_legend.png index dfa1c7211e1..aa797d6593c 100644 Binary files a/test/image/baselines/show_legend.png and b/test/image/baselines/show_legend.png differ diff --git a/test/image/baselines/simple_inset.png b/test/image/baselines/simple_inset.png index 57bb753c15f..3d0619e7d0c 100644 Binary files a/test/image/baselines/simple_inset.png and b/test/image/baselines/simple_inset.png differ diff --git a/test/image/baselines/simple_subplot.png b/test/image/baselines/simple_subplot.png index 8332d85e488..f4157c2fc3b 100644 Binary files a/test/image/baselines/simple_subplot.png and b/test/image/baselines/simple_subplot.png differ diff --git a/test/image/baselines/stacked_bar.png b/test/image/baselines/stacked_bar.png index 5d7cc280df2..6f590eac105 100644 Binary files a/test/image/baselines/stacked_bar.png and b/test/image/baselines/stacked_bar.png differ diff --git a/test/image/baselines/stacked_coupled_subplots.png b/test/image/baselines/stacked_coupled_subplots.png index 78b5d501621..6324f9bafb5 100644 Binary files a/test/image/baselines/stacked_coupled_subplots.png and b/test/image/baselines/stacked_coupled_subplots.png differ diff --git a/test/image/baselines/stacked_subplots.png b/test/image/baselines/stacked_subplots.png index 55663802628..d16f07be42a 100644 Binary files a/test/image/baselines/stacked_subplots.png and b/test/image/baselines/stacked_subplots.png differ diff --git a/test/image/baselines/styling_names.png b/test/image/baselines/styling_names.png index 19ff9c424f7..010c1410b1b 100644 Binary files a/test/image/baselines/styling_names.png and b/test/image/baselines/styling_names.png differ diff --git a/test/image/baselines/text_chart_arrays.png b/test/image/baselines/text_chart_arrays.png index 9c5e580cdbf..26ce503ed07 100644 Binary files a/test/image/baselines/text_chart_arrays.png and b/test/image/baselines/text_chart_arrays.png differ diff --git a/test/image/baselines/text_chart_invalid-arrays.png b/test/image/baselines/text_chart_invalid-arrays.png index 2e2d2d571d2..d08e4a1e63c 100644 Binary files a/test/image/baselines/text_chart_invalid-arrays.png and b/test/image/baselines/text_chart_invalid-arrays.png differ diff --git a/test/image/compare_pixels_test.js b/test/image/compare_pixels_test.js index 260ddf54520..f160070464f 100644 --- a/test/image/compare_pixels_test.js +++ b/test/image/compare_pixels_test.js @@ -98,9 +98,25 @@ function testMock(fileName, t) { var options = { file: diffPath, highlightColor: 'purple', - tolerance: 0.0 + tolerance: 1e-6 }; + /* + * N.B. The non-zero tolerance was added in + * https://github.com/plotly/plotly.js/pull/243 + * where some legend mocks started generating different png outputs + * on `npm run test-image` and `npm run test-image -- mock.json`. + * + * Note that the svg outputs for the problematic mocks were the same + * and playing around with the batch size and timeout durations + * did not seem to affect the results. + * + * With the above tolerance individual `npm run test-image` and + * `npm run test-image -- mock.json` give the same result. + * + * Further investigation is needed. + */ + gm.compare( savedImagePath, path.join(constants.pathToTestImageBaselines, imageFileName), diff --git a/test/image/mocks/legend_scroll.json b/test/image/mocks/legend_scroll.json new file mode 100644 index 00000000000..0c2f4674d20 --- /dev/null +++ b/test/image/mocks/legend_scroll.json @@ -0,0 +1,126 @@ +{ + "data": [ + { + "x": [1,2,3], + "y": [1,2,3], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [2,3,4], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [3,4,5], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [4,5,6], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [5,6,7], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [6,7,8], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [7,8,9], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [8,9,10], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [9,10,11], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [10,11,12], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [11,12,13], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [12,13,14], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [13,14,15], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [14,15,16], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [15,16,17], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [16,17,18], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [17,18,19], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [18,19,20], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [19,20,21], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [20,21,22], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [21,22,23], + "type": "scatter" + }, + { + "x": [1,2,3], + "y": [22,23,24], + "type": "bar" + }, + { + "x": [1,2,3], + "y": [23,24,25], + "type": "scatter" + } + ], + "layout": { + "legend": { + "bordercolor": "#000000", + "borderwidth": 1, + "bgcolor": "#eeffee" + } + } +} diff --git a/test/jasmine/tests/legend_scroll_test.js b/test/jasmine/tests/legend_scroll_test.js new file mode 100644 index 00000000000..e8f02a1661e --- /dev/null +++ b/test/jasmine/tests/legend_scroll_test.js @@ -0,0 +1,89 @@ +var Plotly = require('@lib/index'); +var createGraph = require('../assets/create_graph_div'); +var destroyGraph = require('../assets/destroy_graph_div'); +var mock = require('../../image/mocks/legend_scroll.json'); + +describe('The legend', function() { + var gd, + legend; + + describe('when plotted with many traces', function() { + beforeEach(function() { + gd = createGraph(); + Plotly.plot(gd, mock.data, mock.layout); + legend = document.getElementsByClassName('legend')[0]; + }); + + afterEach(destroyGraph); + + it('should not exceed plot height', function() { + var legendHeight = legend.getAttribute('height'), + plotHeight = gd._fullLayout.height - gd._fullLayout.margin.t - gd._fullLayout.margin.b; + + expect(+legendHeight).toBe(plotHeight); + }); + + it('should insert a scrollbar', function() { + var scrollBar = legend.getElementsByClassName('scrollbar')[0]; + + expect(scrollBar).toBeDefined(); + expect(scrollBar.getAttribute('x')).not.toBe(null); + }); + + it('should scroll when there\'s a wheel event', function() { + var scrollBox = legend.getElementsByClassName('scrollbox')[0]; + + legend.dispatchEvent(scrollTo(100)); + + // Compare against -5 because of a scroll factor of 20 + // ( 100 / 20 === 5 ) + expect(scrollBox.getAttribute('transform')).toBe('translate(0, -5)'); + expect(scrollBox.getAttribute('data-scroll')).toBe('-5'); + }); + + it('should constrain scrolling to the contents', function() { + var scrollBox = legend.getElementsByClassName('scrollbox')[0]; + + legend.dispatchEvent(scrollTo(-100)); + expect(scrollBox.getAttribute('transform')).toBe('translate(0, 0)'); + + legend.dispatchEvent(scrollTo(100000)); + expect(scrollBox.getAttribute('transform')).toBe('translate(0, -179)'); + }); + + it('should scale the scrollbar movement from top to bottom', function() { + var scrollBar = legend.getElementsByClassName('scrollbar')[0], + legendHeight = legend.getAttribute('height'); + + // The scrollbar is 20px tall and has 4px margins + + legend.dispatchEvent(scrollTo(-1000)); + expect(+scrollBar.getAttribute('y')).toBe(4); + + legend.dispatchEvent(scrollTo(10000)); + expect(+scrollBar.getAttribute('y')).toBe(legendHeight - 4 - 20); + }); + }); + + describe('when plotted with few traces', function() { + var gd; + + beforeEach(function() { + gd = createGraph(); + Plotly.plot(gd, [{ x: [1,2,3], y: [2,3,4], name: 'Test' }], {}); + }); + + afterEach(destroyGraph); + + it('should not display the scrollbar', function() { + var scrollBar = document.getElementsByClassName('scrollbar')[0]; + + expect(scrollBar).toBeUndefined(); + }); + }); +}); + + +function scrollTo(delta) { + return new WheelEvent('wheel', { deltaY: delta }); +}