diff --git a/src/components/legend/constants.js b/src/components/legend/constants.js index 6ac1fa2119c..8d7b373f3f4 100644 --- a/src/components/legend/constants.js +++ b/src/components/legend/constants.js @@ -12,5 +12,6 @@ module.exports = { scrollBarWidth: 6, scrollBarMinHeight: 20, scrollBarColor: '#808BA4', - scrollBarMargin: 4 + scrollBarMargin: 4, + textOffsetX: 40 }; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index a7017fda0ac..30d3c66aa7e 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -124,236 +124,239 @@ module.exports = function draw(gd) { .call(setupTraceToggle, gd); }); - if(firstRender) { - computeLegendDimensions(gd, groups, traces); - expandMargin(gd); - } - - // Position and size the legend - var lxMin = 0, - lxMax = fullLayout.width, - lyMin = 0, - lyMax = fullLayout.height; - - computeLegendDimensions(gd, groups, traces); - - if(opts._height > lyMax) { - // If the legend doesn't fit in the plot area, - // do not expand the vertical margins. - expandHorizontalMargin(gd); - } else { - expandMargin(gd); - } + Lib.syncOrAsync([Plots.previousPromises, + function() { + if(firstRender) { + computeLegendDimensions(gd, groups, traces); + expandMargin(gd); + } - // 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); + // Position and size the legend + var lxMin = 0, + lxMax = fullLayout.width, + lyMin = 0, + lyMax = fullLayout.height; - if(anchorUtils.isRightAnchor(opts)) { - lx -= opts._width; - } - else if(anchorUtils.isCenterAnchor(opts)) { - lx -= opts._width / 2; - } + computeLegendDimensions(gd, groups, traces); - if(anchorUtils.isBottomAnchor(opts)) { - ly -= opts._height; - } - else if(anchorUtils.isMiddleAnchor(opts)) { - ly -= opts._height / 2; - } + if(opts._height > lyMax) { + // If the legend doesn't fit in the plot area, + // do not expand the vertical margins. + expandHorizontalMargin(gd); + } else { + expandMargin(gd); + } - // Make sure the legend left and right sides are visible - var legendWidth = opts._width, - legendWidthMax = gs.w; + // 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(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); - } + if(anchorUtils.isRightAnchor(opts)) { + lx -= opts._width; + } + else if(anchorUtils.isCenterAnchor(opts)) { + lx -= opts._width / 2; + } - // Make sure the legend top and bottom are visible - // (legends with a scroll bar are not allowed to stretch beyond the extended - // margins) - var legendHeight = opts._height, - legendHeightMax = gs.h; + if(anchorUtils.isBottomAnchor(opts)) { + ly -= opts._height; + } + else if(anchorUtils.isMiddleAnchor(opts)) { + ly -= opts._height / 2; + } - if(legendHeight > legendHeightMax) { - ly = gs.t; - legendHeight = legendHeightMax; - } - else { - if(ly + legendHeight > lyMax) ly = lyMax - legendHeight; - if(ly < lyMin) ly = lyMin; - legendHeight = Math.min(lyMax - ly, opts._height); - } + // Make sure the legend left and right sides are visible + var legendWidth = opts._width, + legendWidthMax = gs.w; - // Set size and position of all the elements that make up a legend: - // legend, background and border, scroll box and scroll bar - Drawing.setTranslate(legend, lx, ly); - - // to be safe, remove previous listeners - scrollBar.on('.drag', null); - legend.on('wheel', null); - - if(opts._height <= legendHeight || gd._context.staticPlot) { - // if scrollbar should not be shown. - bg.attr({ - width: legendWidth - opts.borderwidth, - height: legendHeight - opts.borderwidth, - x: opts.borderwidth / 2, - y: opts.borderwidth / 2 - }); + 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); + } - Drawing.setTranslate(scrollBox, 0, 0); + // Make sure the legend top and bottom are visible + // (legends with a scroll bar are not allowed to stretch beyond the extended + // margins) + var legendHeight = opts._height, + legendHeightMax = gs.h; - clipPath.select('rect').attr({ - width: legendWidth - 2 * opts.borderwidth, - height: legendHeight - 2 * opts.borderwidth, - x: opts.borderwidth, - y: opts.borderwidth - }); - - Drawing.setClipUrl(scrollBox, clipId); + if(legendHeight > legendHeightMax) { + ly = gs.t; + legendHeight = legendHeightMax; + } + else { + if(ly + legendHeight > lyMax) ly = lyMax - legendHeight; + if(ly < lyMin) ly = lyMin; + legendHeight = Math.min(lyMax - ly, opts._height); + } - Drawing.setRect(scrollBar, 0, 0, 0, 0); - delete opts._scrollY; - } - else { - var scrollBarHeight = Math.max(constants.scrollBarMinHeight, - legendHeight * legendHeight / opts._height); - var scrollBarYMax = legendHeight - - scrollBarHeight - - 2 * constants.scrollBarMargin; - var scrollBoxYMax = opts._height - legendHeight; - var scrollRatio = scrollBarYMax / scrollBoxYMax; - - var scrollBoxY = Math.min(opts._scrollY || 0, scrollBoxYMax); - - // increase the background and clip-path width - // by the scrollbar width and margin - bg.attr({ - width: legendWidth - - 2 * opts.borderwidth + - constants.scrollBarWidth + - constants.scrollBarMargin, - height: legendHeight - opts.borderwidth, - x: opts.borderwidth / 2, - y: opts.borderwidth / 2 - }); + // Set size and position of all the elements that make up a legend: + // legend, background and border, scroll box and scroll bar + Drawing.setTranslate(legend, lx, ly); + + // to be safe, remove previous listeners + scrollBar.on('.drag', null); + legend.on('wheel', null); + + if(opts._height <= legendHeight || gd._context.staticPlot) { + // if scrollbar should not be shown. + bg.attr({ + width: legendWidth - opts.borderwidth, + height: legendHeight - opts.borderwidth, + x: opts.borderwidth / 2, + y: opts.borderwidth / 2 + }); - clipPath.select('rect').attr({ - width: legendWidth - - 2 * opts.borderwidth + - constants.scrollBarWidth + - constants.scrollBarMargin, - height: legendHeight - 2 * opts.borderwidth, - x: opts.borderwidth, - y: opts.borderwidth + scrollBoxY - }); + Drawing.setTranslate(scrollBox, 0, 0); - Drawing.setClipUrl(scrollBox, clipId); + clipPath.select('rect').attr({ + width: legendWidth - 2 * opts.borderwidth, + height: legendHeight - 2 * opts.borderwidth, + x: opts.borderwidth, + y: opts.borderwidth + }); - scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); + Drawing.setClipUrl(scrollBox, clipId); - legend.on('wheel', function() { - scrollBoxY = Lib.constrain( - opts._scrollY + - d3.event.deltaY / scrollBarYMax * scrollBoxYMax, - 0, scrollBoxYMax); - scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); - if(scrollBoxY !== 0 && scrollBoxY !== scrollBoxYMax) { - d3.event.preventDefault(); + Drawing.setRect(scrollBar, 0, 0, 0, 0); + delete opts._scrollY; } - }); - - var eventY0, scrollBoxY0; - - var drag = d3.behavior.drag() - .on('dragstart', function() { - eventY0 = d3.event.sourceEvent.clientY; - scrollBoxY0 = scrollBoxY; - }) - .on('drag', function() { - var e = d3.event.sourceEvent; - if(e.buttons === 2 || e.ctrlKey) return; - - scrollBoxY = Lib.constrain( - (e.clientY - eventY0) / scrollRatio + scrollBoxY0, - 0, scrollBoxYMax); - scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); - }); + else { + var scrollBarHeight = Math.max(constants.scrollBarMinHeight, + legendHeight * legendHeight / opts._height); + var scrollBarYMax = legendHeight - + scrollBarHeight - + 2 * constants.scrollBarMargin; + var scrollBoxYMax = opts._height - legendHeight; + var scrollRatio = scrollBarYMax / scrollBoxYMax; + + var scrollBoxY = Math.min(opts._scrollY || 0, scrollBoxYMax); + + // increase the background and clip-path width + // by the scrollbar width and margin + bg.attr({ + width: legendWidth - + 2 * opts.borderwidth + + constants.scrollBarWidth + + constants.scrollBarMargin, + height: legendHeight - opts.borderwidth, + x: opts.borderwidth / 2, + y: opts.borderwidth / 2 + }); - scrollBar.call(drag); - } + clipPath.select('rect').attr({ + width: legendWidth - + 2 * opts.borderwidth + + constants.scrollBarWidth + + constants.scrollBarMargin, + height: legendHeight - 2 * opts.borderwidth, + x: opts.borderwidth, + y: opts.borderwidth + scrollBoxY + }); + Drawing.setClipUrl(scrollBox, clipId); - function scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio) { - opts._scrollY = gd._fullLayout.legend._scrollY = scrollBoxY; - Drawing.setTranslate(scrollBox, 0, -scrollBoxY); + scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); - Drawing.setRect( - scrollBar, - legendWidth, - constants.scrollBarMargin + scrollBoxY * scrollRatio, - constants.scrollBarWidth, - scrollBarHeight - ); - clipPath.select('rect').attr({ - y: opts.borderwidth + scrollBoxY - }); - } + legend.on('wheel', function() { + scrollBoxY = Lib.constrain( + opts._scrollY + + d3.event.deltaY / scrollBarYMax * scrollBoxYMax, + 0, scrollBoxYMax); + scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); + if(scrollBoxY !== 0 && scrollBoxY !== scrollBoxYMax) { + d3.event.preventDefault(); + } + }); - if(gd._context.edits.legendPosition) { - var xf, yf, x0, y0; + var eventY0, scrollBoxY0; + + var drag = d3.behavior.drag() + .on('dragstart', function() { + eventY0 = d3.event.sourceEvent.clientY; + scrollBoxY0 = scrollBoxY; + }) + .on('drag', function() { + var e = d3.event.sourceEvent; + if(e.buttons === 2 || e.ctrlKey) return; + + scrollBoxY = Lib.constrain( + (e.clientY - eventY0) / scrollRatio + scrollBoxY0, + 0, scrollBoxYMax); + scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio); + }); - legend.classed('cursor-move', true); + scrollBar.call(drag); + } - dragElement.init({ - element: legend.node(), - gd: gd, - prepFn: function() { - var transform = Drawing.getTranslate(legend); - x0 = transform.x; - y0 = transform.y; - }, - moveFn: function(dx, dy) { - var newX = x0 + dx, - newY = y0 + dy; + function scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio) { + opts._scrollY = gd._fullLayout.legend._scrollY = scrollBoxY; + Drawing.setTranslate(scrollBox, 0, -scrollBoxY); - Drawing.setTranslate(legend, newX, newY); + Drawing.setRect( + scrollBar, + legendWidth, + constants.scrollBarMargin + scrollBoxY * scrollRatio, + constants.scrollBarWidth, + scrollBarHeight + ); + clipPath.select('rect').attr({ + y: opts.borderwidth + scrollBoxY + }); + } - xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor); - yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor); - }, - doneFn: function() { - if(xf !== undefined && yf !== undefined) { - Registry.call('relayout', gd, {'legend.x': xf, 'legend.y': yf}); - } - }, - clickFn: function(numClicks, e) { - var clickedTrace = fullLayout._infolayer.selectAll('g.traces').filter(function() { - var bbox = this.getBoundingClientRect(); - return ( - e.clientX >= bbox.left && e.clientX <= bbox.right && - e.clientY >= bbox.top && e.clientY <= bbox.bottom - ); + if(gd._context.edits.legendPosition) { + var xf, yf, x0, y0; + + legend.classed('cursor-move', true); + + dragElement.init({ + element: legend.node(), + gd: gd, + prepFn: function() { + var transform = Drawing.getTranslate(legend); + + x0 = transform.x; + y0 = transform.y; + }, + moveFn: function(dx, dy) { + var newX = x0 + dx, + newY = y0 + dy; + + Drawing.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); + }, + doneFn: function() { + if(xf !== undefined && yf !== undefined) { + Registry.call('relayout', gd, {'legend.x': xf, 'legend.y': yf}); + } + }, + clickFn: function(numClicks, e) { + var clickedTrace = fullLayout._infolayer.selectAll('g.traces').filter(function() { + var bbox = this.getBoundingClientRect(); + return ( + e.clientX >= bbox.left && e.clientX <= bbox.right && + e.clientY >= bbox.top && e.clientY <= bbox.bottom + ); + }); + if(clickedTrace.size() > 0) { + clickOrDoubleClick(gd, legend, clickedTrace, numClicks, e); + } + } }); - if(clickedTrace.size() > 0) { - clickOrDoubleClick(gd, legend, clickedTrace, numClicks, e); - } } - }); - } + }], gd); }; function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) { @@ -412,6 +415,8 @@ function drawTexts(g, gd, maxLength) { .call(Drawing.font, fullLayout.legend.font) .text(isEditable ? ensureLength(name, maxLength) : name); + svgTextUtils.positionText(textEl, constants.textOffsetX, 0); + function textLayout(s) { svgTextUtils.convertToTspans(s, gd, function() { computeTextDimensions(g, gd); @@ -530,9 +535,7 @@ function computeTextDimensions(g, gd) { // approximation to height offset to center the font // to avoid getBoundingClientRect var textY = lineHeight * (0.3 + (1 - textLines) / 2); - // TODO: this 40 should go in a constants file (along with other - // values related to the legend symbol size) - svgTextUtils.positionText(text, 40, textY); + svgTextUtils.positionText(text, constants.textOffsetX, textY); } height = Math.max(height, 16) + 3; diff --git a/test/image/baselines/mathjax.png b/test/image/baselines/mathjax.png index 95f086468c5..ae40a4fcab4 100644 Binary files a/test/image/baselines/mathjax.png and b/test/image/baselines/mathjax.png differ diff --git a/test/image/mocks/mathjax.json b/test/image/mocks/mathjax.json index 743b2100570..064c63b03e0 100644 --- a/test/image/mocks/mathjax.json +++ b/test/image/mocks/mathjax.json @@ -4,18 +4,18 @@ "y": [0, 1.414], "text": ["Hx+yH", "H\\sqrt{2}H"], "mode": "text+markers", - "name": "HE^2=m^2c^4+p^2c^2H" + "name": "$E^2=m^2c^4+p^2c^2$" }, { "x": [0, 1], "y": [1.4, 0.1], "text": ["H1.400 \\pm 0.023H", "H0.100 \\pm 0.002H"], "textposition": "auto", "type": "bar", - "name": "Hx=\\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}H" + "name": "$x=\\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}$" }, { "type": "pie", "values": [1, 9], - "labels": ["H\\frac{1}{10}=10\\%H", "H\\frac{9}{10}=90\\%H"], + "labels": ["$\\frac{1}{10}=10\\%$", "$\\frac{9}{10}=90\\%$"], "textinfo": "label", "domain": {"x": [0.3, 0.75], "y": [0.55, 1]} }, { @@ -43,6 +43,7 @@ }, "height":500, "width":800, + "margin": {"r": 250}, "title": "$i\\hbar\\frac{d\\Psi}{dt}=-[V-\\frac{-\\hbar^2}{2m}\\nabla^2]\\Psi$", "annotations":[ {