diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 99ac85bdf33..2920f1ba6f4 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -35,6 +35,13 @@ module.exports = { font: extendFlat({}, fontAttrs, { description: 'Sets the font used to text the legend items.' }), + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + dflt: 'v', + role: 'info', + description: 'Sets the orientation of the legend.' + }, traceorder: { valType: 'flaglist', flags: ['reversed', 'grouped'], diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index eb95148ce13..aa6e8794ca5 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -21,7 +21,11 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { containerOut = layoutOut.legend = {}; var visibleTraces = 0, - defaultOrder = 'normal'; + defaultOrder = 'normal', + defaultX, + defaultY, + defaultXAnchor, + defaultYAnchor; for(var i = 0; i < fullData.length; i++) { var trace = fullData[i]; @@ -58,12 +62,29 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { coerce('borderwidth'); Lib.coerceFont(coerce, 'font', layoutOut.font); + coerce('orientation'); + if(containerOut.orientation === 'h') { + var xaxis = layoutIn.xaxis; + if(xaxis && xaxis.rangeslider && xaxis.rangeslider.visible) { + defaultX = 0; + defaultXAnchor = 'left'; + defaultY = 1.1; + defaultYAnchor = 'bottom'; + } + else { + defaultX = 0; + defaultXAnchor = 'left'; + defaultY = -0.1; + defaultYAnchor = 'top'; + } + } + coerce('traceorder', defaultOrder); if(helpers.isGrouped(layoutOut.legend)) coerce('tracegroupgap'); - coerce('x'); - coerce('xanchor'); - coerce('y'); - coerce('yanchor'); + coerce('x', defaultX); + coerce('xanchor', defaultXAnchor); + coerce('y', defaultY); + coerce('yanchor', defaultYAnchor); Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); }; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 26641cff7bd..310b81c0ebe 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -101,12 +101,6 @@ module.exports = function draw(gd) { groups.exit().remove(); - if(helpers.isGrouped(opts)) { - groups.attr('transform', function(d, i) { - return 'translate(0,' + i * opts.tracegroupgap + ')'; - }); - } - var traces = groups.selectAll('g.traces') .data(Lib.identity); @@ -122,60 +116,24 @@ module.exports = function draw(gd) { return trace.visible === 'legendonly' ? 0.5 : 1; } }) - .each(function(d, i) { - drawTexts(this, gd, d, i, traces); - - var traceToggle = d3.select(this).selectAll('rect') - .data([0]); - - traceToggle.enter().append('rect') - .classed('legendtoggle', true) - .style('cursor', 'pointer') - .attr('pointer-events', 'all') - .call(Color.fill, 'rgba(0,0,0,0)'); - - traceToggle.on('click', function() { - if(gd._dragged) return; - - var fullData = gd._fullData, - trace = d[0].trace, - legendgroup = trace.legendgroup, - traceIndicesInGroup = [], - tracei, - newVisible; - - if(Plots.traceIs(trace, 'pie')) { - var thisLabel = d[0].label, - newHiddenSlices = hiddenSlices.slice(), - thisLabelIndex = newHiddenSlices.indexOf(thisLabel); - - if(thisLabelIndex === -1) newHiddenSlices.push(thisLabel); - else newHiddenSlices.splice(thisLabelIndex, 1); - - Plotly.relayout(gd, 'hiddenlabels', newHiddenSlices); - } else { - if(legendgroup === '') { - traceIndicesInGroup = [trace.index]; - } else { - for(var i = 0; i < fullData.length; i++) { - tracei = fullData[i]; - if(tracei.legendgroup === legendgroup) { - traceIndicesInGroup.push(tracei.index); - } - } - } - - newVisible = trace.visible === true ? 'legendonly' : true; - Plotly.restyle(gd, 'visible', newVisible, traceIndicesInGroup); - } - }); + .each(function() { + d3.select(this) + .call(drawTexts, gd) + .call(setupTraceToggle, gd); }); + if(gd.firstRender) { + computeLegendDimensions(gd, groups, traces); + expandMargin(gd); + } + // Position and size the legend - var lyMin = 0, + var lxMin = 0, + lxMax = fullLayout.width, + lyMin = 0, lyMax = fullLayout.height; - computeLegendDimensions(gd, traces); + computeLegendDimensions(gd, groups, traces); if(opts.height > lyMax) { // If the legend doesn't fit in the plot area, @@ -206,6 +164,20 @@ module.exports = function draw(gd) { ly -= opts.height / 2; } + // Make sure the legend left and right sides are visible + var legendWidth = opts.width, + legendWidthMax = gs.w; + + if(legendWidth > legendWidthMax) { + lx = gs.l; + legendWidth = legendWidthMax; + } + else { + if(lx + legendWidth > lxMax) lx = lxMax - legendWidth; + if(lx < lxMin) lx = lxMin; + legendWidth = Math.min(lxMax - lx, opts.width); + } + // Make sure the legend top and bottom are visible // (legends with a scroll bar are not allowed to stretch beyond the extended // margins) @@ -217,17 +189,17 @@ module.exports = function draw(gd) { legendHeight = legendHeightMax; } else { - if(ly > lyMax) ly = lyMax - legendHeight; + if(ly + legendHeight > lyMax) ly = lyMax - legendHeight; if(ly < lyMin) ly = lyMin; legendHeight = Math.min(lyMax - ly, opts.height); } // Set size and position of all the elements that make up a legend: // legend, background and border, scroll box and scroll bar - legend.attr('transform', 'translate(' + lx + ',' + ly + ')'); + Lib.setTranslate(legend, lx, ly); bg.attr({ - width: opts.width - opts.borderwidth, + width: legendWidth - opts.borderwidth, height: legendHeight - opts.borderwidth, x: opts.borderwidth / 2, y: opts.borderwidth / 2 @@ -235,10 +207,10 @@ module.exports = function draw(gd) { var scrollPosition = scrollBox.attr('data-scroll') || 0; - scrollBox.attr('transform', 'translate(0, ' + scrollPosition + ')'); + Lib.setTranslate(scrollBox, 0, scrollPosition); clipPath.select('rect').attr({ - width: opts.width - 2 * opts.borderwidth, + width: legendWidth - 2 * opts.borderwidth, height: legendHeight - 2 * opts.borderwidth, x: opts.borderwidth - scrollPosition, y: opts.borderwidth @@ -252,14 +224,14 @@ module.exports = function draw(gd) { // increase the background and clip-path width // by the scrollbar width and margin bg.attr({ - width: opts.width - + width: legendWidth - 2 * opts.borderwidth + constants.scrollBarWidth + constants.scrollBarMargin }); clipPath.select('rect').attr({ - width: opts.width - + width: legendWidth - 2 * opts.borderwidth + constants.scrollBarWidth + constants.scrollBarMargin @@ -310,11 +282,13 @@ module.exports = function draw(gd) { function scrollHandler(scrollBarY, scrollBoxY) { - scrollBox.attr('data-scroll', scrollBoxY); - scrollBox.attr('transform', 'translate(0, ' + scrollBoxY + ')'); + scrollBox + .attr('data-scroll', scrollBoxY) + .call(Lib.setTranslate, 0, scrollBoxY); + scrollBar.call( Drawing.setRect, - opts.width, + legendWidth, scrollBarY, constants.scrollBarWidth, constants.scrollBarHeight @@ -341,8 +315,7 @@ module.exports = function draw(gd) { var newX = x0 + dx, newY = y0 + dy; - var transform = 'translate(' + newX + ', ' + newY + ')'; - legend.attr('transform', transform); + Lib.setTranslate(legend, newX, newY); xf = dragElement.align(newX, 0, gs.l, gs.l+gs.w, opts.xanchor); yf = dragElement.align(newY, 0, gs.t+gs.h, gs.t, opts.yanchor); @@ -356,14 +329,15 @@ module.exports = function draw(gd) { } }; -function drawTexts(context, gd, d, i, traces) { - var fullLayout = gd._fullLayout, - trace = d[0].trace, +function drawTexts(g, gd) { + var legendItem = g.data()[0][0], + fullLayout = gd._fullLayout, + trace = legendItem.trace, isPie = Plots.traceIs(trace, 'pie'), traceIndex = trace.index, - name = isPie ? d[0].label : trace.name; + name = isPie ? legendItem.label : trace.name; - var text = d3.select(context).selectAll('text.legendtext') + var text = g.selectAll('text.legendtext') .data([0]); text.enter().append('text').classed('legendtext', true); text.attr({ @@ -378,12 +352,9 @@ function drawTexts(context, gd, d, i, traces) { function textLayout(s) { Plotly.util.convertToTspans(s, function() { - if(gd.firstRender) { - computeLegendDimensions(gd, traces); - expandMargin(gd); - } + s.selectAll('tspan.line').attr({x: s.attr('x')}); + g.call(computeTextDimensions, gd); }); - s.selectAll('tspan.line').attr({x: s.attr('x')}); } if(gd._context.editable && !isPie) { @@ -400,72 +371,224 @@ function drawTexts(context, gd, d, i, traces) { else text.call(textLayout); } -function computeLegendDimensions(gd, traces) { +function setupTraceToggle(g, gd) { + var hiddenSlices = gd._fullLayout.hiddenlabels ? + gd._fullLayout.hiddenlabels.slice() : + []; + + var traceToggle = g.selectAll('rect') + .data([0]); + + traceToggle.enter().append('rect') + .classed('legendtoggle', true) + .style('cursor', 'pointer') + .attr('pointer-events', 'all') + .call(Color.fill, 'rgba(0,0,0,0)'); + + traceToggle.on('click', function() { + if(gd._dragged) return; + + var legendItem = g.data()[0][0], + fullData = gd._fullData, + trace = legendItem.trace, + legendgroup = trace.legendgroup, + traceIndicesInGroup = [], + tracei, + newVisible; + + if(Plots.traceIs(trace, 'pie')) { + var thisLabel = legendItem.label, + thisLabelIndex = hiddenSlices.indexOf(thisLabel); + + if(thisLabelIndex === -1) hiddenSlices.push(thisLabel); + else hiddenSlices.splice(thisLabelIndex, 1); + + Plotly.relayout(gd, 'hiddenlabels', hiddenSlices); + } else { + if(legendgroup === '') { + traceIndicesInGroup = [trace.index]; + } else { + for(var i = 0; i < fullData.length; i++) { + tracei = fullData[i]; + if(tracei.legendgroup === legendgroup) { + traceIndicesInGroup.push(tracei.index); + } + } + } + + newVisible = trace.visible === true ? 'legendonly' : true; + Plotly.restyle(gd, 'visible', newVisible, traceIndicesInGroup); + } + }); +} + +function computeTextDimensions(g, gd) { + var legendItem = g.data()[0][0], + bg = g.selectAll('.legendtoggle'), + mathjaxGroup = g.select('g[class*=math-group]'), + opts = gd._fullLayout.legend, + lineHeight = opts.font.size * 1.3, + height, + width; + + if(!legendItem.trace.showlegend) { + g.remove(); + return; + } + + if(mathjaxGroup.node()) { + var mathjaxBB = Drawing.bBox(mathjaxGroup.node()); + + height = mathjaxBB.height; + width = mathjaxBB.width; + + Lib.setTranslate(mathjaxGroup, 0, (height / 4)); + } + else { + var text = g.selectAll('.legendtext'), + textSpans = g.selectAll('.legendtext>tspan'), + textLines = textSpans[0].length || 1; + + height = lineHeight * textLines; + width = text.node() && Drawing.bBox(text.node()).width; + + // approximation to height offset to center the font + // to avoid getBoundingClientRect + var textY = lineHeight * (0.3 + (1 - textLines) / 2); + text.attr('y', textY); + textSpans.attr('y', textY); + } + + height = Math.max(height, 16) + 3; + + bg.attr({x: 0, y: -height / 2, height: height}); + + legendItem.height = height; + legendItem.width = width; +} + +function computeLegendDimensions(gd, groups, traces) { var fullLayout = gd._fullLayout, opts = fullLayout.legend, - borderwidth = opts.borderwidth; - - opts.width = 0; - opts.height = 0; - - traces.each(function(d) { - var trace = d[0].trace, - g = d3.select(this), - bg = g.selectAll('.legendtoggle'), - text = g.selectAll('.legendtext'), - tspans = g.selectAll('.legendtext>tspan'), - tHeight = opts.font.size * 1.3, - tLines = tspans[0].length || 1, - tWidth = text.node() && Drawing.bBox(text.node()).width, - mathjaxGroup = g.select('g[class*=math-group]'), - textY, - tHeightFull; - - if(!trace.showlegend) { - g.remove(); - return; + borderwidth = opts.borderwidth, + isGrouped = helpers.isGrouped(opts); + + if(helpers.isVertical(opts)) { + if(isGrouped) { + groups.each(function(d, i) { + Lib.setTranslate(this, 0, i * opts.tracegroupgap); + }); } - if(mathjaxGroup.node()) { - var mathjaxBB = Drawing.bBox(mathjaxGroup.node()); - tHeight = mathjaxBB.height; - tWidth = mathjaxBB.width; - mathjaxGroup.attr('transform','translate(0,' + (tHeight / 4) + ')'); + opts.width = 0; + opts.height = 0; + + traces.each(function(d) { + var legendItem = d[0], + textHeight = legendItem.height, + textWidth = legendItem.width; + + Lib.setTranslate(this, + borderwidth, + (5 + borderwidth + opts.height + textHeight / 2)); + + opts.height += textHeight; + opts.width = Math.max(opts.width, textWidth); + }); + + opts.width += 45 + borderwidth * 2; + opts.height += 10 + borderwidth * 2; + + if(isGrouped) { + opts.height += (opts._lgroupsLength-1) * opts.tracegroupgap; } - else { - // approximation to height offset to center the font - // to avoid getBoundingClientRect - textY = tHeight * (0.3 + (1 - tLines) / 2); - text.attr('y', textY); - tspans.attr('y', textY); + + traces.selectAll('.legendtoggle') + .attr('width', (gd._context.editable ? 0 : opts.width) + 40); + + // make sure we're only getting full pixels + opts.width = Math.ceil(opts.width); + opts.height = Math.ceil(opts.height); + } + else if(isGrouped) { + opts.width = 0; + opts.height = 0; + + var groupXOffsets = [opts.width], + groupData = groups.data(); + + for(var i = 0, n = groupData.length; i < n; i++) { + var textWidths = groupData[i].map(function(legendItemArray) { + return legendItemArray[0].width; + }); + + var groupWidth = 40 + Math.max.apply(null, textWidths); + + opts.width += opts.tracegroupgap + groupWidth; + + groupXOffsets.push(opts.width); } - tHeightFull = Math.max(tHeight * tLines, 16) + 3; + groups.each(function(d, i) { + Lib.setTranslate(this, groupXOffsets[i], 0); + }); - g.attr('transform', - 'translate(' + borderwidth + ',' + - (5 + borderwidth + opts.height + tHeightFull / 2) + - ')' - ); - bg.attr({x: 0, y: -tHeightFull / 2, height: tHeightFull}); + groups.each(function() { + var group = d3.select(this), + groupTraces = group.selectAll('g.traces'), + groupHeight = 0; - opts.height += tHeightFull; - opts.width = Math.max(opts.width, tWidth || 0); - }); + groupTraces.each(function(d) { + var legendItem = d[0], + textHeight = legendItem.height; + + Lib.setTranslate(this, + 0, + (5 + borderwidth + groupHeight + textHeight / 2)); - opts.width += 45 + borderwidth * 2; - opts.height += 10 + borderwidth * 2; + groupHeight += textHeight; + }); + + opts.height = Math.max(opts.height, groupHeight); + }); - if(helpers.isGrouped(opts)) { - opts.height += (opts._lgroupsLength-1) * opts.tracegroupgap; + opts.height += 10 + borderwidth * 2; + opts.width += borderwidth * 2; + + // make sure we're only getting full pixels + opts.width = Math.ceil(opts.width); + opts.height = Math.ceil(opts.height); + + traces.selectAll('.legendtoggle') + .attr('width', (gd._context.editable ? 0 : opts.width)); } + else { + opts.width = 0; + opts.height = 0; - traces.selectAll('.legendtoggle') - .attr('width', (gd._context.editable ? 0 : opts.width) + 40); + traces.each(function(d) { + var legendItem = d[0], + traceWidth = 40 + legendItem.width, + traceGap = opts.tracegroupgap || 5; - // make sure we're only getting full pixels - opts.width = Math.ceil(opts.width); - opts.height = Math.ceil(opts.height); + Lib.setTranslate(this, + (borderwidth + opts.width), + (5 + borderwidth + legendItem.height / 2)); + + opts.width += traceGap + traceWidth; + opts.height = Math.max(opts.height, legendItem.height); + }); + + opts.width += borderwidth * 2; + opts.height += 10 + borderwidth * 2; + + // make sure we're only getting full pixels + opts.width = Math.ceil(opts.width); + opts.height = Math.ceil(opts.height); + + traces.selectAll('.legendtoggle') + .attr('width', (gd._context.editable ? 0 : opts.width)); + } } function expandMargin(gd) { diff --git a/src/components/legend/helpers.js b/src/components/legend/helpers.js index cf300a4a9da..5a481fca152 100644 --- a/src/components/legend/helpers.js +++ b/src/components/legend/helpers.js @@ -20,6 +20,10 @@ exports.isGrouped = function isGrouped(legendLayout) { return (legendLayout.traceorder || '').indexOf('grouped') !== -1; }; +exports.isVertical = function isVertical(legendLayout) { + return legendLayout.orientation !== 'h'; +}; + exports.isReversed = function isReversed(legendLayout) { return (legendLayout.traceorder || '').indexOf('reversed') !== -1; }; diff --git a/test/image/baselines/legend_horizontal.png b/test/image/baselines/legend_horizontal.png new file mode 100644 index 00000000000..a218a2bc4f4 Binary files /dev/null and b/test/image/baselines/legend_horizontal.png differ diff --git a/test/image/baselines/legend_horizontal_groups.png b/test/image/baselines/legend_horizontal_groups.png new file mode 100644 index 00000000000..7df8bd805e8 Binary files /dev/null and b/test/image/baselines/legend_horizontal_groups.png differ diff --git a/test/image/baselines/pseudo_html.png b/test/image/baselines/pseudo_html.png index d15e9753126..269d6ef1f8a 100644 Binary files a/test/image/baselines/pseudo_html.png and b/test/image/baselines/pseudo_html.png differ diff --git a/test/image/mocks/legend_horizontal.json b/test/image/mocks/legend_horizontal.json new file mode 100644 index 00000000000..ed0423bf114 --- /dev/null +++ b/test/image/mocks/legend_horizontal.json @@ -0,0 +1,207 @@ +{ + "data": [ + { + "x": [ + 0, + 0.3571429, + 0.7142857, + 1.071429, + 1.428571, + 1.785714, + 2.142857, + 2.5, + 2.857143, + 3.214286, + 3.571429, + 3.928571, + 4.285714, + 4.642857, + 5 + ], + "y": [ + 0.869693, + 1.088434, + 0.758308, + 1.849319, + 1.728616, + 0.9908515, + 1.519909, + 1.083975, + 0.3153477, + 0.04181056, + -0.3400965, + -0.1003989, + -0.6768811, + -0.8555722, + -0.4287511 + ], + "name": "trace 0", + "type": "scatter" + }, + { + "x": [ + 0, + 1, + 2, + 3, + 4, + 5 + ], + "y": [ + 1, + 0.5, + 0.7, + -1.2, + 0.3, + 0.4 + ], + "name": "trace 1", + "type": "bar" + } + ], + "layout": { + "title": "Click to enter Plot title", + "titlefont": { + "color": "", + "family": "", + "size": 0 + }, + "font": { + "family": "'Open sans', verdana, arial, sans-serif", + "size": 12, + "color": "#444" + }, + "showlegend": true, + "autosize": true, + "width": 1152, + "height": 481, + "xaxis": { + "title": "Click to enter X axis title", + "titlefont": { + "color": "", + "family": "", + "size": 0 + }, + "range": [ + -0.5, + 5.5 + ], + "domain": [ + 0, + 1 + ], + "type": "linear", + "rangemode": "normal", + "showgrid": false, + "zeroline": false, + "showline": false, + "autotick": true, + "nticks": 0, + "ticks": "", + "showticklabels": true, + "tick0": 0, + "dtick": 1, + "ticklen": 5, + "tickwidth": 1, + "tickcolor": "#444", + "tickangle": "auto", + "tickfont": { + "family": "", + "size": 0, + "color": "" + }, + "exponentformat": "B", + "showexponent": "all", + "gridcolor": "#eee", + "gridwidth": 1, + "zerolinecolor": "#444", + "zerolinewidth": 1, + "linecolor": "#444", + "linewidth": 1, + "anchor": "y", + "position": 0, + "mirror": false, + "overlaying": false, + "autorange": true + }, + "yaxis": { + "title": "Click to enter Y axis title", + "titlefont": { + "color": "", + "family": "", + "size": 0 + }, + "range": [ + -1.3694066111111112, + 2.018725611111111 + ], + "domain": [ + 0, + 1 + ], + "type": "linear", + "rangemode": "normal", + "showgrid": true, + "zeroline": true, + "showline": false, + "autotick": true, + "nticks": 0, + "ticks": "", + "showticklabels": true, + "tick0": 0, + "dtick": 0.5, + "ticklen": 5, + "tickwidth": 1, + "tickcolor": "#444", + "tickangle": "auto", + "tickfont": { + "family": "", + "size": 0, + "color": "" + }, + "exponentformat": "B", + "showexponent": "all", + "gridcolor": "#eee", + "gridwidth": 1, + "zerolinecolor": "#444", + "zerolinewidth": 1, + "linecolor": "#444", + "linewidth": 1, + "anchor": "x", + "position": 0, + "mirror": false, + "overlaying": false, + "autorange": true + }, + "legend": { + "orientation": "h", + "traceorder": "normal", + "font": { + "family": "", + "size": 0, + "color": "" + }, + "bgcolor": "#fff", + "bordercolor": "#444", + "borderwidth": 1 + }, + "margin": { + "l": 80, + "r": 80, + "b": 80, + "t": 100, + "pad": 0, + "autoexpand": true + }, + "paper_bgcolor": "#fff", + "plot_bgcolor": "#fff", + "hovermode": "x", + "dragmode": "zoom", + "barmode": "group", + "bargap": 0.2, + "bargroupgap": 0, + "boxmode": "overlay", + "separators": ".,", + "hidesources": false + } +} diff --git a/test/image/mocks/legend_horizontal_groups.json b/test/image/mocks/legend_horizontal_groups.json new file mode 100644 index 00000000000..ee67ea2cf51 --- /dev/null +++ b/test/image/mocks/legend_horizontal_groups.json @@ -0,0 +1,119 @@ +{ + "data": [ + { + "type": "scatter", + "x": [ + 1, + 2, + 3 + ], + "y": [ + 2, + 1, + 2 + ], + "legendgroup": "group" + }, + { + "type": "box", + "x": [ + 1, + 2, + 3 + ], + "y": [ + 1, + 2, + 1 + ], + "legendgroup": "group", + "showlegend": false + }, + { + "type": "bar", + "x": [ + 1, + 2, + 3 + ], + "y": [ + 2, + 1, + 2 + ], + "legendgroup": "group 2" + }, + { + "type": "scatter", + "x": [ + 3, + 4, + 5 + ], + "y": [ + 2, + 1, + 2 + ], + "legendgroup": "group" + }, + { + "type": "scatter", + "x": [ + 3, + 4, + 5 + ], + "y": [ + 1, + 2, + 1 + ] + }, + { + "type": "bar", + "x": [ + 3, + 4, + 5 + ], + "y": [ + 2, + 1, + 2 + ], + "legendgroup": "group 2", + "showlegend": false + } + ], + "layout": { + "legend": { + "x": 0, + "xanchor": "left", + "y": -0.1, + "yanchor": "top", + "orientation": "h", + "tracegroupgap": 20 + }, + "xaxis": { + "type": "linear", + "range": [ + 0.5, + 5.5 + ], + "autorange": true + }, + "yaxis": { + "type": "linear", + "range": [ + 0, + 2.136498516320475 + ], + "autorange": true + }, + "height": 450, + "width": 1000, + "autosize": true, + "showlegend": true + } +} diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index 34ef1e618a2..21a35794ec9 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -1,4 +1,5 @@ var Plots = require('@src/plots/plots'); +var Lib = require('@src/lib'); var Legend = require('@src/components/legend'); var getLegendData = require('@src/components/legend/get_legend_data'); @@ -65,6 +66,46 @@ describe('Test legend:', function() { expect(layoutOut.legend.traceorder).toEqual('grouped+reversed'); }); + it('should default orientation to vertical', function() { + supplyLayoutDefaults(layoutIn, layoutOut, []); + expect(layoutOut.legend.orientation).toEqual('v'); + }); + + describe('for horizontal legends', function() { + var layoutInForHorizontalLegends; + + beforeEach(function() { + layoutInForHorizontalLegends = Lib.extendDeep({ + legend: { + orientation: 'h' + }, + xaxis: { + rangeslider: { + visible: false + } + } + }, layoutIn); + }); + + it('should default position to bottom left', function() { + supplyLayoutDefaults(layoutInForHorizontalLegends, layoutOut, []); + expect(layoutOut.legend.x).toEqual(0); + expect(layoutOut.legend.xanchor).toEqual('left'); + expect(layoutOut.legend.y).toEqual(-0.1); + expect(layoutOut.legend.yanchor).toEqual('top'); + }); + + it('should default position to top left if a range slider present', function() { + var mockLayoutIn = Lib.extendDeep({}, layoutInForHorizontalLegends); + mockLayoutIn.xaxis.rangeslider.visible = true; + + supplyLayoutDefaults(mockLayoutIn, layoutOut, []); + expect(layoutOut.legend.x).toEqual(0); + expect(layoutOut.legend.xanchor).toEqual('left'); + expect(layoutOut.legend.y).toEqual(1.1); + expect(layoutOut.legend.yanchor).toEqual('bottom'); + }); + }); }); describe('getLegendData', function() {