From 18ddd5dbe9717c67364ae64c1eaaafc41eba3a6b Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 6 Mar 2020 18:19:28 -0500 Subject: [PATCH 01/28] hover: move text logic into function getHoverLabelText() --- src/components/fx/hover.js | 143 +++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 68 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index b2bfc185213..cadbea685bf 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -941,8 +941,6 @@ function createHoverText(hoverData, opts, gd) { // and figure out sizes hoverLabels.each(function(d) { var g = d3.select(this).attr('transform', ''); - var name = ''; - var text = ''; // combine possible non-opaque trace color with bgColor var color0 = d.bgcolor || d.color; @@ -959,72 +957,9 @@ function createHoverText(hoverData, opts, gd) { // find a contrasting color for border and text var contrastColor = d.borderColor || Color.contrast(numsColor); - // to get custom 'name' labels pass cleanPoint - if(d.nameOverride !== undefined) d.name = d.nameOverride; - - if(d.name) { - if(d.trace._meta) { - d.name = Lib.templateString(d.name, d.trace._meta); - } - name = plainText(d.name, d.nameLength); - } - - if(d.zLabel !== undefined) { - if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '
'; - if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '
'; - if(d.trace.type !== 'choropleth' && d.trace.type !== 'choroplethmapbox') { - text += (text ? 'z: ' : '') + d.zLabel; - } - } else if(showCommonLabel && d[hovermode + 'Label'] === t0) { - text = d[(hovermode === 'x' ? 'y' : 'x') + 'Label'] || ''; - } else if(d.xLabel === undefined) { - if(d.yLabel !== undefined && d.trace.type !== 'scattercarpet') { - text = d.yLabel; - } - } else if(d.yLabel === undefined) text = d.xLabel; - else text = '(' + d.xLabel + ', ' + d.yLabel + ')'; - - if((d.text || d.text === 0) && !Array.isArray(d.text)) { - text += (text ? '
' : '') + d.text; - } - - // used by other modules (initially just ternary) that - // manage their own hoverinfo independent of cleanPoint - // the rest of this will still apply, so such modules - // can still put things in (x|y|z)Label, text, and name - // and hoverinfo will still determine their visibility - if(d.extraText !== undefined) text += (text ? '
' : '') + d.extraText; - - // if 'text' is empty at this point, - // and hovertemplate is not defined, - // put 'name' in main label and don't show secondary label - if(text === '' && !d.hovertemplate) { - // if 'name' is also empty, remove entire label - if(name === '') g.remove(); - text = name; - } - - // hovertemplate - var d3locale = fullLayout._d3locale; - var hovertemplate = d.hovertemplate || false; - var hovertemplateLabels = d.hovertemplateLabels || d; - var eventData = d.eventData[0] || {}; - if(hovertemplate) { - text = Lib.hovertemplateString( - hovertemplate, - hovertemplateLabels, - d3locale, - eventData, - d.trace._meta - ); - - text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { - // assign name for secondary text label - name = plainText(extra, d.nameLength); - // remove from main text label - return ''; - }); - } + var texts = getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g); + var text = texts[0]; + var name = texts[1]; // main label var tx = g.select('text.nums') @@ -1123,6 +1058,78 @@ function createHoverText(hoverData, opts, gd) { return hoverLabels; } +function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) { + var name = ''; + var text = ''; + // to get custom 'name' labels pass cleanPoint + if(d.nameOverride !== undefined) d.name = d.nameOverride; + + if(d.name) { + if(d.trace._meta) { + d.name = Lib.templateString(d.name, d.trace._meta); + } + name = plainText(d.name, d.nameLength); + } + + if(d.zLabel !== undefined) { + if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '
'; + if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '
'; + if(d.trace.type !== 'choropleth' && d.trace.type !== 'choroplethmapbox') { + text += (text ? 'z: ' : '') + d.zLabel; + } + } else if(showCommonLabel && d[hovermode.charAt(0) + 'Label'] === t0) { + text = d[(hovermode.charAt(0) === 'x' ? 'y' : 'x') + 'Label'] || ''; + } else if(d.xLabel === undefined) { + if(d.yLabel !== undefined && d.trace.type !== 'scattercarpet') { + text = d.yLabel; + } + } else if(d.yLabel === undefined) text = d.xLabel; + else text = '(' + d.xLabel + ', ' + d.yLabel + ')'; + + if((d.text || d.text === 0) && !Array.isArray(d.text)) { + text += (text ? '
' : '') + d.text; + } + + // used by other modules (initially just ternary) that + // manage their own hoverinfo independent of cleanPoint + // the rest of this will still apply, so such modules + // can still put things in (x|y|z)Label, text, and name + // and hoverinfo will still determine their visibility + if(d.extraText !== undefined) text += (text ? '
' : '') + d.extraText; + + // if 'text' is empty at this point, + // and hovertemplate is not defined, + // put 'name' in main label and don't show secondary label + if(g && text === '' && !d.hovertemplate) { + // if 'name' is also empty, remove entire label + if(name === '') g.remove(); + text = name; + } + + // hovertemplate + var d3locale = fullLayout._d3locale; + var hovertemplate = d.hovertemplate || false; + var hovertemplateLabels = d.hovertemplateLabels || d; + var eventData = d.eventData[0] || {}; + if(hovertemplate) { + text = Lib.hovertemplateString( + hovertemplate, + hovertemplateLabels, + d3locale, + eventData, + d.trace._meta + ); + + text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { + // assign name for secondary text label + name = plainText(extra, d.nameLength); + // remove from main text label + return ''; + }); + } + return [text, name]; +} + // Make groups of touching points, and within each group // move each point so that no labels overlap, but the average // label position is the same as it was before moving. Indicentally, From 1c925117265d5ff09cedcb6609258090bbd1f97f Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 6 Mar 2020 18:21:48 -0500 Subject: [PATCH 02/28] introduce hovermode 'x unified' and 'y unified' --- src/components/fx/helpers.js | 2 +- src/components/fx/hover.js | 98 ++++++++++++- src/components/fx/layout_attributes.js | 2 +- src/components/legend/defaults.js | 8 +- src/components/legend/draw.js | 100 ++++++++----- src/components/legend/get_legend_data.js | 3 +- src/components/legend/style.js | 4 +- test/image/baselines/hovermode_xunified.png | Bin 0 -> 23313 bytes test/image/mocks/hovermode_xunified.json | 45 ++++++ test/jasmine/tests/hover_label_test.js | 155 ++++++++++++++++++++ test/jasmine/tests/legend_test.js | 1 + 11 files changed, 364 insertions(+), 54 deletions(-) create mode 100644 test/image/baselines/hovermode_xunified.png create mode 100644 test/image/mocks/hovermode_xunified.json diff --git a/src/components/fx/helpers.js b/src/components/fx/helpers.js index 83846707fa7..ce1011edb2f 100644 --- a/src/components/fx/helpers.js +++ b/src/components/fx/helpers.js @@ -54,7 +54,7 @@ exports.p2c = function p2c(axArray, v) { exports.getDistanceFunction = function getDistanceFunction(mode, dx, dy, dxy) { if(mode === 'closest') return dxy || exports.quadrature(dx, dy); - return mode === 'x' ? dx : dy; + return mode.charAt(0) === 'x' ? dx : dy; }; exports.getClosest = function getClosest(cd, distfn, pointData) { diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index cadbea685bf..3bef67f1bc4 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -25,6 +25,8 @@ var Registry = require('../../registry'); var helpers = require('./helpers'); var constants = require('./constants'); +var legend = require('../legend'); + // hover labels for multiple horizontal bars get tilted by some angle, // then need to be offset differently if they overlap var YANGLE = constants.YANGLE; @@ -244,7 +246,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { if(hovermode && !supportsCompare) hovermode = 'closest'; - if(['x', 'y', 'closest'].indexOf(hovermode) === -1 || !gd.calcdata || + if(['x', 'y', 'closest', 'x unified', 'y unified'].indexOf(hovermode) === -1 || !gd.calcdata || gd.querySelector('.zoombox') || gd._dragging) { return dragElement.unhoverRaw(gd, evt); } @@ -388,6 +390,9 @@ function _hover(gd, evt, subplot, noHoverEvent) { // within one trace mode can sometimes be overridden mode = hovermode; + if(['x unified', 'y unified'].indexOf(mode) !== -1) { + mode = mode.charAt(0); + } // container for new point, also used to pass info into module.hoverPoints pointData = { @@ -543,7 +548,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { var thisSpikeDistance; for(var i = 0; i < pointsData.length; i++) { thisSpikeDistance = pointsData[i].spikeDistance; - if(thisSpikeDistance < minDistance && thisSpikeDistance <= spikedistance) { + if(thisSpikeDistance <= minDistance && thisSpikeDistance <= spikedistance) { resultPoint = pointsData[i]; minDistance = thisSpikeDistance; } @@ -661,9 +666,10 @@ function _hover(gd, evt, subplot, noHoverEvent) { var hoverLabels = createHoverText(hoverData, labelOpts, gd); - hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); - - alignHoverText(hoverLabels, rotateLabels); + if(['x unified', 'y unified'].indexOf(hovermode) === -1) { + hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); + alignHoverText(hoverLabels, rotateLabels); + } // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true // we should improve the "fx" API so other plots can use it without these hack. @@ -712,7 +718,7 @@ function createHoverText(hoverData, opts, gd) { var c0 = hoverData[0]; var xa = c0.xa; var ya = c0.ya; - var commonAttr = hovermode === 'y' ? 'yLabel' : 'xLabel'; + var commonAttr = hovermode.charAt(0) === 'y' ? 'yLabel' : 'xLabel'; var t0 = c0[commonAttr]; var t00 = (String(t0) || '').split(' ')[0]; var outerContainerBB = outerContainer.node().getBoundingClientRect(); @@ -906,11 +912,87 @@ function createHoverText(hoverData, opts, gd) { // remove the "close but not quite" points // because of error bars, only take up to a space - hoverData = hoverData.filter(function(d) { + hoverData = filterClosePoints(hoverData); + }); + + function filterClosePoints(hoverData) { + return hoverData.filter(function(d) { return (d.zLabelVal !== undefined) || (d[commonAttr] || '').split(' ')[0] === t00; }); - }); + } + + // Show a single hover label + if(['x unified', 'y unified'].indexOf(hovermode) !== -1) { + // Delete leftover hover labels from other hovermodes + container.selectAll('g.hovertext').remove(); + + // similarly to compare mode, we remove the "close but not quite together" points + hoverData = filterClosePoints(hoverData); + + // mock legend + var mockLayoutIn = { + showlegend: true, + legend: { + title: {text: t0}, + bgcolor: fullLayout.paper_bgcolor, + borderwidth: 1, + tracegroupgap: 7 + } + }; + var mockLayoutOut = {}; + legend.supplyLayoutDefaults(mockLayoutIn, mockLayoutOut, gd._fullData, fullLayout); + var legendOpts = mockLayoutOut.legend; + + // prepare items for the legend + legendOpts.entries = []; + for(var j = 0; j < hoverData.length; j++) { + var texts = getHoverLabelText(hoverData[j], true, hovermode, fullLayout, t0); + var text = texts[0]; + var name = texts[1]; + hoverData[j].text = name + ' : ' + text; + hoverData[j].name = name; + legendOpts.entries.push([hoverData[j]]); + } + legendOpts.layer = container; + + // Draw unified hover label + legend.draw(gd, legendOpts); + + // Position the hover + var ly = Lib.mean(hoverData.map(function(c) {return (c.y0 + c.y1) / 2;})); + var lx = Lib.mean(hoverData.map(function(c) {return (c.x0 + c.x1) / 2;})); + var legendContainer = container.select('g.legend'); + var tbb = legendContainer.node().getBoundingClientRect(); + lx += xa._offset; + ly += ya._offset - tbb.height / 2; + + // Change horizontal alignment to end up on screen + var txWidth = tbb.width + 2 * HOVERTEXTPAD; + var anchorStartOK = lx + txWidth <= outerWidth; + var anchorEndOK = lx - txWidth >= 0; + if(!anchorStartOK && anchorEndOK) { + lx -= txWidth; + } else { + lx += 2 * HOVERTEXTPAD; + } + + // Change vertical alignement to end up on screen + var txHeight = tbb.height + 2 * HOVERTEXTPAD; + var overflowTop = ly <= outerTop; + var overflowBottom = ly + txHeight >= outerHeight; + var canFit = txHeight <= outerHeight; + if(canFit) { + if(overflowTop) { + ly = tbb.top - outerTop + 2 * HOVERTEXTPAD; + } else if(overflowBottom) { + ly = outerHeight - txHeight; + } + } + legendContainer.attr('transform', 'translate(' + lx + ',' + ly + ')'); + + return legendContainer; + } // show all the individual labels diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index 2ade6b3d1ee..c855590b867 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -57,7 +57,7 @@ module.exports = { hovermode: { valType: 'enumerated', role: 'info', - values: ['x', 'y', 'closest', false], + values: ['x', 'y', 'closest', false, 'x unified', 'y unified'], editType: 'modebar', description: [ 'Determines the mode of hover interactions.', diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index 9077a1e7890..3af4a46f77c 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -17,7 +17,7 @@ var basePlotLayoutAttributes = require('../../plots/layout_attributes'); var helpers = require('./helpers'); -module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { +module.exports = function legendDefaults(layoutIn, layoutOut, fullData, fullLayout) { var containerIn = layoutIn.legend || {}; var legendTraceCount = 0; @@ -82,10 +82,10 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { if(showLegend === false) return; - coerce('bgcolor', layoutOut.paper_bgcolor); + coerce('bgcolor', (fullLayout || layoutOut).paper_bgcolor); coerce('bordercolor'); coerce('borderwidth'); - Lib.coerceFont(coerce, 'font', layoutOut.font); + Lib.coerceFont(coerce, 'font', (fullLayout || layoutOut).font); var orientation = coerce('orientation'); var defaultX, defaultY, defaultYAnchor; @@ -127,6 +127,6 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { var titleText = coerce('title.text'); if(titleText) { coerce('title.side', orientation === 'h' ? 'left' : 'top'); - Lib.coerceFont(coerce, 'title.font', layoutOut.font); + Lib.coerceFont(coerce, 'title.font', (fullLayout || layoutOut).font); } }; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index c5610577de7..3b60026e3e1 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -30,26 +30,44 @@ var getLegendData = require('./get_legend_data'); var style = require('./style'); var helpers = require('./helpers'); -module.exports = function draw(gd) { +module.exports = function draw(gd, opts) { var fullLayout = gd._fullLayout; var clipId = 'legend' + fullLayout._uid; + var layer; - if(!fullLayout._infolayer || !gd.calcdata) return; + // Check whether this is the main legend (ie. called without any opts) + if(!opts) { + opts = fullLayout.legend || {}; + opts._main = true; + layer = fullLayout._infolayer; + } else { + layer = opts.layer; + clipId += '-hover'; + } + + if(!layer) return; if(!gd._legendMouseDownTime) gd._legendMouseDownTime = 0; - var opts = fullLayout.legend; - var legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts); + var legendData; + if(opts._main) { + if(!gd.calcdata) return; + legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts); + } else { + if(!opts.entries) return; + legendData = getLegendData(opts.entries, opts); + } + var hiddenSlices = fullLayout.hiddenlabels || []; - if(!fullLayout.showlegend || !legendData.length) { - fullLayout._infolayer.selectAll('.legend').remove(); + if(opts._main && (!fullLayout.showlegend || !legendData.length)) { + layer.selectAll('.legend').remove(); fullLayout._topdefs.select('#' + clipId).remove(); return Plots.autoMargin(gd, 'legend'); } - var legend = Lib.ensureSingle(fullLayout._infolayer, 'g', 'legend', function(s) { - s.attr('pointer-events', 'all'); + var legend = Lib.ensureSingle(layer, 'g', 'legend', function(s) { + if(opts._main) s.attr('pointer-events', 'all'); }); var clipPath = Lib.ensureSingleById(fullLayout._topdefs, 'clipPath', clipId, function(s) { @@ -75,7 +93,7 @@ module.exports = function draw(gd) { .call(Drawing.font, title.font) .text(title.text); - textLayout(titleEl, scrollBox, gd); // handle mathjax or multi-line text and compute title height + textLayout(titleEl, scrollBox, gd, opts); // handle mathjax or multi-line text and compute title height } var scrollBar = Lib.ensureSingle(legend, 'rect', 'scrollbar', function(s) { @@ -99,18 +117,18 @@ module.exports = function draw(gd) { return trace.visible === 'legendonly' ? 0.5 : 1; } }) - .each(function() { d3.select(this).call(drawTexts, gd); }) - .call(style, gd) - .each(function() { d3.select(this).call(setupTraceToggle, gd); }); + .each(function() { d3.select(this).call(drawTexts, gd, opts); }) + .call(style, gd, opts) + .each(function() { if(opts._main) d3.select(this).call(setupTraceToggle, gd); }); Lib.syncOrAsync([ Plots.previousPromises, - function() { return computeLegendDimensions(gd, groups, traces); }, + function() { return computeLegendDimensions(gd, groups, traces, opts); }, function() { // IF expandMargin return a Promise (which is truthy), // we're under a doAutoMargin redraw, so we don't have to // draw the remaining pieces below - if(expandMargin(gd)) return; + if(opts._main && expandMargin(gd)) return; var gs = fullLayout._size; var bw = opts.borderwidth; @@ -118,7 +136,7 @@ module.exports = function draw(gd) { var lx = gs.l + gs.w * opts.x - FROM_TL[getXanchor(opts)] * opts._width; var ly = gs.t + gs.h * (1 - opts.y) - FROM_TL[getYanchor(opts)] * opts._effHeight; - if(fullLayout.margin.autoexpand) { + if(opts._main && fullLayout.margin.autoexpand) { var lx0 = lx; var ly0 = ly; @@ -141,11 +159,16 @@ module.exports = function draw(gd) { scrollBar.on('.drag', null); legend.on('wheel', null); - if(opts._height <= opts._maxHeight || gd._context.staticPlot) { + if(!opts._main || opts._height <= opts._maxHeight || gd._context.staticPlot) { // if scrollbar should not be shown. + var height = opts._effHeight; + + // if not the main legend, let it be its full size + if(!opts._main) height = opts._height; + bg.attr({ width: opts._width - bw, - height: opts._effHeight - bw, + height: height - bw, x: bw / 2, y: bw / 2 }); @@ -154,7 +177,7 @@ module.exports = function draw(gd) { clipPath.select('rect').attr({ width: opts._width - 2 * bw, - height: opts._effHeight - 2 * bw, + height: height - 2 * bw, x: bw, y: bw }); @@ -310,7 +333,7 @@ module.exports = function draw(gd) { } }, clickFn: function(numClicks, e) { - var clickedTrace = fullLayout._infolayer.selectAll('g.traces').filter(function() { + var clickedTrace = layer.selectAll('g.traces').filter(function() { var bbox = this.getBoundingClientRect(); return ( e.clientX >= bbox.left && e.clientX <= bbox.right && @@ -364,19 +387,22 @@ function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) { } } -function drawTexts(g, gd) { +function drawTexts(g, gd, opts) { var legendItem = g.data()[0][0]; - var fullLayout = gd._fullLayout; - var opts = fullLayout.legend; var trace = legendItem.trace; var isPieLike = Registry.traceIs(trace, 'pie-like'); var traceIndex = trace.index; - var isEditable = gd._context.edits.legendText && !isPieLike; + var isEditable = opts._main && gd._context.edits.legendText && !isPieLike; var maxNameLength = opts._maxNameLength; - var name = isPieLike ? legendItem.label : trace.name; - if(trace._meta) { - name = Lib.templateString(name, trace._meta); + var name; + if(!opts.entries) { + name = isPieLike ? legendItem.label : trace.name; + if(trace._meta) { + name = Lib.templateString(name, trace._meta); + } + } else { + name = legendItem.text; } var textEl = Lib.ensureSingle(g, 'text', 'legendtext'); @@ -390,10 +416,10 @@ function drawTexts(g, gd) { if(isEditable) { textEl.call(svgTextUtils.makeEditable, {gd: gd, text: name}) - .call(textLayout, g, gd) + .call(textLayout, g, gd, opts) .on('edit', function(newName) { this.text(ensureLength(newName, maxNameLength)) - .call(textLayout, g, gd); + .call(textLayout, g, gd, opts); var fullInput = legendItem.trace._fullInput || {}; var update = {}; @@ -414,7 +440,7 @@ function drawTexts(g, gd) { return Registry.call('_guiRestyle', gd, update, traceIndex); }); } else { - textLayout(textEl, g, gd); + textLayout(textEl, g, gd, opts); } } @@ -467,23 +493,23 @@ function setupTraceToggle(g, gd) { }); } -function textLayout(s, g, gd) { +function textLayout(s, g, gd, opts) { svgTextUtils.convertToTspans(s, gd, function() { - computeTextDimensions(g, gd); + computeTextDimensions(g, gd, opts); }); } -function computeTextDimensions(g, gd) { +function computeTextDimensions(g, gd, opts) { var legendItem = g.data()[0][0]; - if(legendItem && !legendItem.trace.showlegend) { + if(opts._main && legendItem && !legendItem.trace.showlegend) { g.remove(); return; } var mathjaxGroup = g.select('g[class*=math-group]'); var mathjaxNode = mathjaxGroup.node(); - var bw = gd._fullLayout.legend.borderwidth; - var opts = gd._fullLayout.legend; + if(!opts) opts = gd._fullLayout.legend; + var bw = opts.borderwidth; var lineHeight = (legendItem ? opts : opts.title).font.size * LINE_SPACING; var height, width; @@ -555,9 +581,9 @@ function getTitleSize(opts) { * - _width: legend width * - _maxWidth (for orientation:h only): maximum width before starting new row */ -function computeLegendDimensions(gd, groups, traces) { +function computeLegendDimensions(gd, groups, traces, opts) { var fullLayout = gd._fullLayout; - var opts = fullLayout.legend; + if(!opts) opts = fullLayout.legend; var gs = fullLayout._size; var isVertical = helpers.isVertical(opts); diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 71909d54210..abfca91ce4e 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -19,6 +19,7 @@ module.exports = function getLegendData(calcdata, opts) { var lgroupi = 0; var maxNameLength = 0; var i, j; + var main = opts._main; function addOneItem(legendGroup, legendItem) { // each '' legend group is treated as a separate group @@ -44,7 +45,7 @@ module.exports = function getLegendData(calcdata, opts) { var trace = cd0.trace; var lgroup = trace.legendgroup; - if(!trace.visible || !trace.showlegend) continue; + if(main && (!trace.visible || !trace.showlegend)) continue; if(Registry.traceIs(trace, 'pie-like')) { if(!slicesShown[lgroup]) slicesShown[lgroup] = {}; diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 6525a34b995..7c7faffb5ad 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -26,9 +26,9 @@ var CST_MARKER_LINE_WIDTH = 2; var MAX_LINE_WIDTH = 10; var MAX_MARKER_LINE_WIDTH = 5; -module.exports = function style(s, gd) { +module.exports = function style(s, gd, legend) { var fullLayout = gd._fullLayout; - var legend = fullLayout.legend; + if(!legend) legend = fullLayout.legend; var constantItemSizing = legend.itemsizing === 'constant'; var boundLineWidth = function(mlw, cont, max, cst) { diff --git a/test/image/baselines/hovermode_xunified.png b/test/image/baselines/hovermode_xunified.png new file mode 100644 index 0000000000000000000000000000000000000000..44b018d828292c49b823fb4db5bc634bdf03a503 GIT binary patch literal 23313 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 Date: Fri, 6 Mar 2020 18:22:10 -0500 Subject: [PATCH 03/28] unified hoverlabel: add smart defaults for spikelines --- src/components/fx/layout_defaults.js | 6 +++++- src/plots/cartesian/layout_defaults.js | 12 +++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/fx/layout_defaults.js b/src/components/fx/layout_defaults.js index 42941f83cba..70931e3e730 100644 --- a/src/components/fx/layout_defaults.js +++ b/src/components/fx/layout_defaults.js @@ -35,8 +35,12 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var hoverMode = coerce('hovermode', hovermodeDflt); if(hoverMode) { + var dflt; + if(['x unified', 'y unified'].indexOf(hoverMode) !== -1) { + dflt = -1; + } coerce('hoverdistance'); - coerce('spikedistance'); + coerce('spikedistance', dflt); } // if only mapbox or geo subplots is present on graph, diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 21c651c245e..54b7c30466b 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -249,12 +249,14 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions); handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut); - var spikecolor = coerce2('spikecolor'); - var spikethickness = coerce2('spikethickness'); - var spikedash = coerce2('spikedash'); - var spikemode = coerce2('spikemode'); + var unifiedHover = layoutIn.hovermode && ['x unified', 'y unified'].indexOf(layoutIn.hovermode) !== -1; + var unifiedSpike = unifiedHover && axLetter === layoutIn.hovermode.charAt(0); + var spikecolor = coerce2('spikecolor', unifiedHover ? axLayoutOut.color : undefined); + var spikethickness = coerce2('spikethickness', unifiedHover ? 1.5 : undefined); + var spikedash = coerce2('spikedash', unifiedHover ? 'dot' : undefined); + var spikemode = coerce2('spikemode', unifiedHover ? 'across' : undefined); var spikesnap = coerce2('spikesnap'); - var showSpikes = coerce('showspikes', !!spikecolor || !!spikethickness || !!spikedash || !!spikemode || !!spikesnap); + var showSpikes = coerce('showspikes', unifiedSpike || !!spikecolor || !!spikethickness || !!spikedash || !!spikemode || !!spikesnap); if(!showSpikes) { delete axLayoutOut.spikecolor; From 2bc9c77ff159c0c75e63bb6222f08dd3f7377429 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 9 Mar 2020 10:13:53 -0400 Subject: [PATCH 04/28] unified hoverlabel: test label's background color --- test/jasmine/tests/hover_label_test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 5f1a6f2be79..5149772cd36 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3684,6 +3684,13 @@ describe('hovermode: (x|y)unified', function() { }); } + function assertStyle(color) { + var hoverLayer = d3.select('g.hoverlayer'); + var hover = hoverLayer.select('g.legend'); + var bg = hover.select('rect.bg'); + expect(bg.node().style.fill).toBe(color); + } + it('set smart defaults for spikeline in x unified', function(done) { Plotly.newPlot(gd, [{y: [4, 6, 5]}], {'hovermode': 'x unified', 'xaxis': {'color': 'red'}}) .then(function(gd) { @@ -3762,6 +3769,20 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); + it('label should have color of paper_bgcolor', function(done) { + var mockCopy = Lib.extendDeep({}, mock); + var bgcolor = 'rgb(15, 200, 85)'; + mockCopy.layout.paper_bgcolor = bgcolor; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertStyle(bgcolor); + }) + .catch(failTest) + .then(done); + }); + it('should work with hovertemplate', function(done) { var mockCopy = Lib.extendDeep({}, mock); mockCopy.data[0].hovertemplate = 'hovertemplate: %{y:0.2f}'; From 3fe5951b53be3cd126d7f13632c6798b712d352b Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 9 Mar 2020 10:20:14 -0400 Subject: [PATCH 05/28] unified hoverlabel: do not display trace name if it's empty --- src/components/fx/hover.js | 7 ++++++- test/jasmine/tests/hover_label_test.js | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 3bef67f1bc4..5dac2884c8f 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -950,8 +950,13 @@ function createHoverText(hoverData, opts, gd) { var texts = getHoverLabelText(hoverData[j], true, hovermode, fullLayout, t0); var text = texts[0]; var name = texts[1]; - hoverData[j].text = name + ' : ' + text; hoverData[j].name = name; + if(name) { + hoverData[j].text = name + ' : ' + text; + } else { + hoverData[j].text = text; + } + legendOpts.entries.push([hoverData[j]]); } legendOpts.layer = container; diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 5149772cd36..93bf1c1df25 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3795,6 +3795,16 @@ describe('hovermode: (x|y)unified', function() { 'trace 0 : hovertemplate: 4.00', 'name : 3.00 8.00' ]}); + + return Plotly.restyle(gd, 'hovertemplate', '%{y:0.2f}'); + }) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertLabel({title: '3', items: [ + '4.00', + '8.00' + ]}); }) .catch(failTest) .then(done); From 88bb33a6f8007d6e330dd269682af49614546e53 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 9 Mar 2020 12:59:23 -0400 Subject: [PATCH 06/28] unified hoverlabel: fix positioning for edge cases --- src/components/fx/hover.js | 5 +++-- src/components/legend/draw.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 5dac2884c8f..ed42a3115ac 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -937,7 +937,8 @@ function createHoverText(hoverData, opts, gd) { title: {text: t0}, bgcolor: fullLayout.paper_bgcolor, borderwidth: 1, - tracegroupgap: 7 + tracegroupgap: 7, + orientation: 'v' } }; var mockLayoutOut = {}; @@ -989,7 +990,7 @@ function createHoverText(hoverData, opts, gd) { var canFit = txHeight <= outerHeight; if(canFit) { if(overflowTop) { - ly = tbb.top - outerTop + 2 * HOVERTEXTPAD; + ly = ya._offset + 2 * HOVERTEXTPAD; } else if(overflowBottom) { ly = outerHeight - txHeight; } diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 3b60026e3e1..a46ab69c569 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -153,7 +153,7 @@ module.exports = function draw(gd, opts) { // Set size and position of all the elements that make up a legend: // legend, background and border, scroll box and scroll bar as well as title - Drawing.setTranslate(legend, lx, ly); + if(opts._main) Drawing.setTranslate(legend, lx, ly); // to be safe, remove previous listeners scrollBar.on('.drag', null); From 07fb8afb5c1d92264cb3fb4d52a5e51a4790f8f4 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 9 Mar 2020 12:59:42 -0400 Subject: [PATCH 07/28] unified hoverlabel: test finance trace --- test/jasmine/tests/hover_label_test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 93bf1c1df25..56d95d9d898 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3769,6 +3769,23 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); + it('should for finance traces', function(done) { + var mockOhlc = require('@mocks/finance_multicategory.json'); + var mockCopy = Lib.extendDeep({}, mockOhlc); + mockCopy.layout.hovermode = 'x unified'; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, {curveNumber: 0, pointNumber: 0}); + + assertLabel({title: 'Group 2 - b', items: [ + 'ohlc : open: 12high: 17low: 9close: 13 â–²', + 'candlestick : open: 22high: 27low: 19close: 23 â–²' + ]}); + }) + .catch(failTest) + .then(done); + }); + it('label should have color of paper_bgcolor', function(done) { var mockCopy = Lib.extendDeep({}, mock); var bgcolor = 'rgb(15, 200, 85)'; From a704769af258535f86b26ade7ad4f3a1c6a9061e Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 9 Mar 2020 13:34:42 -0400 Subject: [PATCH 08/28] unified hoverlabel: fix for cases when hoverData is empty otherwise console logs errors for mock "box-alignment-offset" --- src/components/fx/hover.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index ed42a3115ac..5c8071e1732 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -962,6 +962,9 @@ function createHoverText(hoverData, opts, gd) { } legendOpts.layer = container; + // Return early if nothing is hovered on + if(legendOpts.entries.length === 0) return; + // Draw unified hover label legend.draw(gd, legendOpts); From 9ea8bfdab29495a2eaf96884a36dff71a38aff67 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 10 Mar 2020 13:32:01 -0400 Subject: [PATCH 09/28] unified hoverlabel: revert changes to legend/defaults.js --- src/components/fx/hover.js | 10 ++++++---- src/components/legend/defaults.js | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 5c8071e1732..c5a90d2fd6c 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -25,7 +25,8 @@ var Registry = require('../../registry'); var helpers = require('./helpers'); var constants = require('./constants'); -var legend = require('../legend'); +var legendSupplyDefaults = require('../legend/defaults'); +var legendDraw = require('../legend/draw'); // hover labels for multiple horizontal bars get tilted by some angle, // then need to be offset differently if they overlap @@ -934,7 +935,8 @@ function createHoverText(hoverData, opts, gd) { var mockLayoutIn = { showlegend: true, legend: { - title: {text: t0}, + title: {text: t0, font: fullLayout.title.font}, + font: fullLayout.font, bgcolor: fullLayout.paper_bgcolor, borderwidth: 1, tracegroupgap: 7, @@ -942,7 +944,7 @@ function createHoverText(hoverData, opts, gd) { } }; var mockLayoutOut = {}; - legend.supplyLayoutDefaults(mockLayoutIn, mockLayoutOut, gd._fullData, fullLayout); + legendSupplyDefaults(mockLayoutIn, mockLayoutOut, gd._fullData); var legendOpts = mockLayoutOut.legend; // prepare items for the legend @@ -966,7 +968,7 @@ function createHoverText(hoverData, opts, gd) { if(legendOpts.entries.length === 0) return; // Draw unified hover label - legend.draw(gd, legendOpts); + legendDraw(gd, legendOpts); // Position the hover var ly = Lib.mean(hoverData.map(function(c) {return (c.y0 + c.y1) / 2;})); diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index 3af4a46f77c..9077a1e7890 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -17,7 +17,7 @@ var basePlotLayoutAttributes = require('../../plots/layout_attributes'); var helpers = require('./helpers'); -module.exports = function legendDefaults(layoutIn, layoutOut, fullData, fullLayout) { +module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { var containerIn = layoutIn.legend || {}; var legendTraceCount = 0; @@ -82,10 +82,10 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData, fullLayo if(showLegend === false) return; - coerce('bgcolor', (fullLayout || layoutOut).paper_bgcolor); + coerce('bgcolor', layoutOut.paper_bgcolor); coerce('bordercolor'); coerce('borderwidth'); - Lib.coerceFont(coerce, 'font', (fullLayout || layoutOut).font); + Lib.coerceFont(coerce, 'font', layoutOut.font); var orientation = coerce('orientation'); var defaultX, defaultY, defaultYAnchor; @@ -127,6 +127,6 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData, fullLayo var titleText = coerce('title.text'); if(titleText) { coerce('title.side', orientation === 'h' ? 'left' : 'top'); - Lib.coerceFont(coerce, 'title.font', (fullLayout || layoutOut).font); + Lib.coerceFont(coerce, 'title.font', layoutOut.font); } }; From 36bc6b5dc2f27ed3c2b90066c0b1afc4199c692e Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 10 Mar 2020 13:43:25 -0400 Subject: [PATCH 10/28] unified hoverlabel: use stricter comparison operators --- src/components/fx/hover.js | 2 +- src/plots/cartesian/layout_defaults.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index c5a90d2fd6c..163e7504eef 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -954,7 +954,7 @@ function createHoverText(hoverData, opts, gd) { var text = texts[0]; var name = texts[1]; hoverData[j].name = name; - if(name) { + if(name !== '') { hoverData[j].text = name + ' : ' + text; } else { hoverData[j].text = text; diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 54b7c30466b..cd2fe435a95 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -256,7 +256,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var spikedash = coerce2('spikedash', unifiedHover ? 'dot' : undefined); var spikemode = coerce2('spikemode', unifiedHover ? 'across' : undefined); var spikesnap = coerce2('spikesnap'); - var showSpikes = coerce('showspikes', unifiedSpike || !!spikecolor || !!spikethickness || !!spikedash || !!spikemode || !!spikesnap); + var showSpikes = coerce('showspikes', !!unifiedSpike || !!spikecolor || !!spikethickness || !!spikedash || !!spikemode || !!spikesnap); if(!showSpikes) { delete axLayoutOut.spikecolor; From e82e010da369a347d8d13db18290b34ca710db79 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 10 Mar 2020 13:49:00 -0400 Subject: [PATCH 11/28] unified hoverlabel: :hocho: unused mock/baseline --- test/image/baselines/hovermode_xunified.png | Bin 23313 -> 0 bytes test/image/mocks/hovermode_xunified.json | 45 -------------------- test/jasmine/tests/hover_label_test.js | 6 ++- 3 files changed, 5 insertions(+), 46 deletions(-) delete mode 100644 test/image/baselines/hovermode_xunified.png delete mode 100644 test/image/mocks/hovermode_xunified.json diff --git a/test/image/baselines/hovermode_xunified.png b/test/image/baselines/hovermode_xunified.png deleted file mode 100644 index 44b018d828292c49b823fb4db5bc634bdf03a503..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23313 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 Date: Tue, 10 Mar 2020 13:50:57 -0400 Subject: [PATCH 12/28] hover: revert unnecessary modification --- src/components/fx/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 163e7504eef..8a2d8167e6e 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -549,7 +549,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { var thisSpikeDistance; for(var i = 0; i < pointsData.length; i++) { thisSpikeDistance = pointsData[i].spikeDistance; - if(thisSpikeDistance <= minDistance && thisSpikeDistance <= spikedistance) { + if(thisSpikeDistance < minDistance && thisSpikeDistance <= spikedistance) { resultPoint = pointsData[i]; minDistance = thisSpikeDistance; } From 4bf9052bed80ed74942411c7c7af4facf45358fd Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 10 Mar 2020 15:31:02 -0400 Subject: [PATCH 13/28] unified hoverlabel: title font should be the same as fullLayout.font --- src/components/fx/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 8a2d8167e6e..c33ff7e7a3f 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -935,7 +935,7 @@ function createHoverText(hoverData, opts, gd) { var mockLayoutIn = { showlegend: true, legend: { - title: {text: t0, font: fullLayout.title.font}, + title: {text: t0, font: fullLayout.font}, font: fullLayout.font, bgcolor: fullLayout.paper_bgcolor, borderwidth: 1, From 609d5cacc32bd963f1cfd7c06b533f1bde7f7305 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 10 Mar 2020 15:32:25 -0400 Subject: [PATCH 14/28] unified hoverlabel: do not process MathJax in hover since it's not supported without this fix, in mock "ohlc_first", the library tries to render MathJax and the result is broken --- src/components/legend/draw.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index a46ab69c569..b9122e4ccef 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -494,6 +494,7 @@ function setupTraceToggle(g, gd) { } function textLayout(s, g, gd, opts) { + if(!opts._main) s.attr('data-notex', true); // do not process MathJax if not main svgTextUtils.convertToTspans(s, gd, function() { computeTextDimensions(g, gd, opts); }); From ec8c373ecdd3b98f4478e6afa52151418dc0fd69 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 10 Mar 2020 15:33:23 -0400 Subject: [PATCH 15/28] unified hoverlabel: style legend item using marker's style --- src/components/fx/hover.js | 21 +++++-- src/components/legend/style.js | 5 +- test/jasmine/tests/hover_label_test.js | 81 +++++++++++++++++++++++++- 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index c33ff7e7a3f..84237da0cc9 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -953,14 +953,27 @@ function createHoverText(hoverData, opts, gd) { var texts = getHoverLabelText(hoverData[j], true, hovermode, fullLayout, t0); var text = texts[0]; var name = texts[1]; - hoverData[j].name = name; + var pt = hoverData[j]; + pt.name = name; if(name !== '') { - hoverData[j].text = name + ' : ' + text; + pt.text = name + ' : ' + text; } else { - hoverData[j].text = text; + pt.text = text; } - legendOpts.entries.push([hoverData[j]]); + // pass through marker's calcdata to style legend items + var cd = pt.cd[pt.index]; + if(cd) { + if(cd.mc) pt.mc = cd.mc; + if(cd.mcc) pt.mc = cd.mcc; + if(cd.mlc) pt.mlc = cd.mlc; + if(cd.mlcc) pt.mlc = cd.mlcc; + if(cd.mlw) pt.mlw = cd.mlw; + if(cd.mrc) pt.mrc = cd.mrc; + } + pt._distinct = true; + + legendOpts.entries.push([pt]); } legendOpts.layer = container; diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 7c7faffb5ad..3cc1a7b8d1b 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -213,7 +213,10 @@ module.exports = function style(s, gd, legend) { return valToBound; } - function pickFirst(array) { return array[0]; } + function pickFirst(array) { + if(d0._distinct && d0.index && array[d0.index]) return array[d0.index]; + return array[0]; + } // constrain text, markers, etc so they'll fit on the legend if(showMarkers || showText || showLines) { diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 93a4a96a1ca..351f4c935e1 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3688,13 +3688,28 @@ describe('hovermode: (x|y)unified', function() { }); } - function assertStyle(color) { + function assertBgcolor(color) { var hoverLayer = d3.select('g.hoverlayer'); var hover = hoverLayer.select('g.legend'); var bg = hover.select('rect.bg'); expect(bg.node().style.fill).toBe(color); } + function assertSymbol(exp) { + var hoverLayer = d3.select('g.hoverlayer'); + var hover = hoverLayer.select('g.legend'); + var traces = hover.selectAll('g.traces'); + traces.each(function(d, i) { + var pts = d3.select(this).selectAll('g.legendpoints path'); + pts.each(function() { + var node = d3.select(this).node(); + expect(node.style.fill).toBe(exp[i][0], 'wrong fill for point ' + i); + expect(node.style.strokeWidth).toBe(exp[i][1], 'wrong stroke-width for point ' + i); + expect(node.style.stroke).toBe(exp[i][2], 'wrong stroke for point ' + i); + }); + }); + } + it('set smart defaults for spikeline in x unified', function(done) { Plotly.newPlot(gd, [{y: [4, 6, 5]}], {'hovermode': 'x unified', 'xaxis': {'color': 'red'}}) .then(function(gd) { @@ -3773,7 +3788,7 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); - it('should for finance traces', function(done) { + it('should work for finance traces', function(done) { var mockOhlc = require('@mocks/finance_multicategory.json'); var mockCopy = Lib.extendDeep({}, mockOhlc); mockCopy.layout.hovermode = 'x unified'; @@ -3790,6 +3805,66 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); + it('should style scatter symbols accordingly', function(done) { + var mock = require('@mocks/marker_colorscale_template.json'); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.hovermode = 'x unified'; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, {xval: 1}); + assertLabel({title: '1', items: ['2']}); + assertSymbol([['rgb(33, 145, 140)', '0px', '']]); + }) + .then(function() { + _hover(gd, {xval: 2}); + assertLabel({title: '2', items: ['3']}); + assertSymbol([['rgb(253, 231, 37)', '0px', '']]); + }) + .catch(failTest) + .then(done); + }); + + it('should style bar symbols accordingly', function(done) { + var mock = require('@mocks/bar-marker-line-colorscales.json'); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.hovermode = 'x unified'; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, {xval: 10}); + assertLabel({title: '10', items: ['10']}); + assertSymbol([['rgb(94, 216, 43)', '4px', 'rgb(197, 232, 190)']]); + }) + .then(function() { + _hover(gd, {xval: 20}); + assertLabel({title: '20', items: ['20']}); + assertSymbol([['rgb(168, 140, 33)', '4px', 'rgb(111, 193, 115)']]); + }) + .catch(failTest) + .then(done); + }); + + it('should style funnel symbols accordingly', function(done) { + var mock = require('@mocks/funnel_custom.json'); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.hovermode = 'x unified'; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, {xval: 1}); + // assertLabel({title: 'B', items: ['asdf', 'asdf']}); + assertSymbol([ + ['rgb(0, 255, 0)', '0px', ''], + ['rgb(255, 255, 0)', '5px', 'rgb(0, 0, 127)'] + ]); + }) + .then(function() { + _hover(gd, {xval: 4}); + // assertLabel({title: 'E', items: ['asdf', 'asdf']}); + // assertSymbol([['rgb(168, 140, 33)', '4px', 'rgb(111, 193, 115)']]); + }) + .catch(failTest) + .then(done); + }); + it('label should have color of paper_bgcolor', function(done) { var mockCopy = Lib.extendDeep({}, mock); var bgcolor = 'rgb(15, 200, 85)'; @@ -3798,7 +3873,7 @@ describe('hovermode: (x|y)unified', function() { .then(function(gd) { _hover(gd, { xval: 3 }); - assertStyle(bgcolor); + assertBgcolor(bgcolor); }) .catch(failTest) .then(done); From 0822b93bb08b3009f0495bf8de5f9d56f031305f Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 00:23:40 -0400 Subject: [PATCH 16/28] unified hoverlabel: inherit traceorder from the the legend --- src/components/fx/hover.js | 1 + test/jasmine/tests/hover_label_test.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 84237da0cc9..b0d88e6e430 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -940,6 +940,7 @@ function createHoverText(hoverData, opts, gd) { bgcolor: fullLayout.paper_bgcolor, borderwidth: 1, tracegroupgap: 7, + traceorder: fullLayout.legend ? fullLayout.legend.traceorder : undefined, orientation: 'v' } }; diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 351f4c935e1..a381b25d10e 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3788,6 +3788,27 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); + it('should order items in the same way as the legend', function(done) { + var mock = require('@mocks/stacked_area.json'); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.hovermode = 'x unified'; + var expectation = ['top : 1', 'middle : 6', 'bottom : 0']; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertLabel({title: '3', items: expectation}); + return Plotly.relayout(gd, 'legend.traceorder', 'normal'); + }) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertLabel({title: '3', items: expectation.reverse()}); + }) + .catch(failTest) + .then(done); + }); + it('should work for finance traces', function(done) { var mockOhlc = require('@mocks/finance_multicategory.json'); var mockCopy = Lib.extendDeep({}, mockOhlc); From ae5b9ccfd0fc3a0f45467b4aa721f1b5e06def4b Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 02:19:32 -0400 Subject: [PATCH 17/28] unified hoverlabel: fix filtering logic --- src/components/fx/hover.js | 2 +- test/jasmine/tests/hover_label_test.js | 49 ++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index b0d88e6e430..396a55cfa30 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -929,7 +929,7 @@ function createHoverText(hoverData, opts, gd) { container.selectAll('g.hovertext').remove(); // similarly to compare mode, we remove the "close but not quite together" points - hoverData = filterClosePoints(hoverData); + if((t0 !== undefined) && (c0.distance <= opts.hoverdistance)) hoverData = filterClosePoints(hoverData); // mock legend var mockLayoutIn = { diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index a381b25d10e..e9b7351bad8 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3666,8 +3666,8 @@ describe('hovermode: (x|y)unified', function() { Lib.clearThrottle(); } - function assertElementCount(className, size) { - var g = d3.selectAll('g.' + className); + function assertElementCount(selector, size) { + var g = d3.selectAll(selector); expect(g.size()).toBe(size); } @@ -3788,6 +3788,28 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); + it('shares filtering logic with compare mode x', function(done) { + var mock = require('@mocks/27.json'); + var mockCopy = Lib.extendDeep({}, mock); + + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, { xval: '2002' }); + assertElementCount('g.hovertext', 2); + + return Plotly.relayout(gd, 'hovermode', 'x unified'); + }) + .then(function() { + _hover(gd, { xval: '2002' }); + assertLabel({title: '2002.042', items: [ + 'Market income : 0.5537845', + 'Market incom... : 0.4420997' + ]}); + }) + .catch(failTest) + .then(done); + }); + it('should order items in the same way as the legend', function(done) { var mock = require('@mocks/stacked_area.json'); var mockCopy = Lib.extendDeep({}, mock); @@ -3826,6 +3848,21 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); + it('should work for "legend_horizontal_autowrap"', function(done) { + var mock = require('@mocks/legend_horizontal_autowrap.json'); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.hovermode = 'x unified'; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, {xval: 1}); + + assertElementCount('g.hoverlayer g.legend', 1); + assertElementCount('g.hoverlayer g.traces', 20); + }) + .catch(failTest) + .then(done); + }); + it('should style scatter symbols accordingly', function(done) { var mock = require('@mocks/marker_colorscale_template.json'); var mockCopy = Lib.extendDeep({}, mock); @@ -3934,16 +3971,16 @@ describe('hovermode: (x|y)unified', function() { .then(function(gd) { _hover(gd, { xval: 3 }); - assertElementCount('hovertext', 2); - assertElementCount('legend', 0); + assertElementCount('g.hovertext', 2); + assertElementCount('g.legend', 0); return Plotly.relayout(gd, 'hovermode', 'x unified'); }) .then(function(gd) { _hover(gd, { xval: 3 }); - assertElementCount('hovertext', 0); - assertElementCount('legend', 1); + assertElementCount('g.hovertext', 0); + assertElementCount('g.legend', 1); }) .catch(failTest) .then(done); From c0e1bf7bbb90476a4ab3fa6f348337ff4659a03c Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 12:24:29 -0400 Subject: [PATCH 18/28] unified hoverlabel: fix waterfall symbol --- src/components/fx/hover.js | 1 + src/components/legend/style.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 396a55cfa30..41e6e90696a 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -971,6 +971,7 @@ function createHoverText(hoverData, opts, gd) { if(cd.mlcc) pt.mlc = cd.mlcc; if(cd.mlw) pt.mlw = cd.mlw; if(cd.mrc) pt.mrc = cd.mrc; + if(cd.dir) pt.dir = cd.dir; } pt._distinct = true; diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 3cc1a7b8d1b..0fe933bea3e 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -291,6 +291,14 @@ module.exports = function style(s, gd, legend) { function styleWaterfalls(d) { var trace = d[0].trace; + if(d[0]._distinct && d[0].trace[d[0].dir]) { + var cont = d[0].trace[d[0].dir].marker; + d[0].mc = cont.color; + d[0].mlw = cont.line.width; + d[0].mlc = cont.line.color; + return styleBarLike(d, this, 'waterfall'); + } + var ptsData = []; if(trace.visible && trace.type === 'waterfall') { ptsData = d[0].hasTotals ? From bca4a708026a9bf3378561e87f8c094929256547 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 12:47:44 -0400 Subject: [PATCH 19/28] unified hoverlabel: test waterfall symbol --- src/components/legend/style.js | 2 +- test/jasmine/tests/hover_label_test.js | 27 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 0fe933bea3e..396dfb1fbb7 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -291,7 +291,7 @@ module.exports = function style(s, gd, legend) { function styleWaterfalls(d) { var trace = d[0].trace; - if(d[0]._distinct && d[0].trace[d[0].dir]) { + if(d[0]._distinct && d[0].dir && trace[d[0].dir] && trace[d[0].dir].marker) { var cont = d[0].trace[d[0].dir].marker; d[0].mc = cont.color; d[0].mlw = cont.line.width; diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index e9b7351bad8..ccf6d403d53 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3699,6 +3699,8 @@ describe('hovermode: (x|y)unified', function() { var hoverLayer = d3.select('g.hoverlayer'); var hover = hoverLayer.select('g.legend'); var traces = hover.selectAll('g.traces'); + expect(traces.size()).toBe(exp.length); + traces.each(function(d, i) { var pts = d3.select(this).selectAll('g.legendpoints path'); pts.each(function() { @@ -3923,6 +3925,31 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); + it('should style waterfall symbols correctly', function(done) { + var mock = require('@mocks/waterfall_custom.json'); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.hovermode = 'x unified'; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, {xval: 4}); + assertSymbol([ + ['rgb(255, 65, 54)', '0px', ''] + ]); + return Plotly.restyle(gd, { + 'decreasing.marker.line.width': 5, + 'decreasing.marker.line.color': 'violet' + }); + }) + .then(function(gd) { + _hover(gd, {xval: 4}); + assertSymbol([ + ['rgb(255, 65, 54)', '5px', 'rgb(238, 130, 238)'] + ]); + }) + .catch(failTest) + .then(done); + }); + it('label should have color of paper_bgcolor', function(done) { var mockCopy = Lib.extendDeep({}, mock); var bgcolor = 'rgb(15, 200, 85)'; From 8308111c5fcfba2d595a54771614f2b5beec6504 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 12:53:24 -0400 Subject: [PATCH 20/28] unified hoverlabel: only apply bar-like styling of symbol to waterfall --- src/components/legend/style.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 396dfb1fbb7..402a4accd19 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -291,7 +291,7 @@ module.exports = function style(s, gd, legend) { function styleWaterfalls(d) { var trace = d[0].trace; - if(d[0]._distinct && d[0].dir && trace[d[0].dir] && trace[d[0].dir].marker) { + if(d[0]._distinct && trace.type === 'waterfall') { var cont = d[0].trace[d[0].dir].marker; d[0].mc = cont.color; d[0].mlw = cont.line.width; From 33a037be3a8b18fdb87186ac33aecb4180e16ba5 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 13:18:49 -0400 Subject: [PATCH 21/28] unified hoverlabel: fix code style --- src/components/legend/style.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 402a4accd19..b7b9bec7618 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -290,8 +290,9 @@ module.exports = function style(s, gd, legend) { function styleWaterfalls(d) { var trace = d[0].trace; + var isWaterfall = trace.type === 'waterfall'; - if(d[0]._distinct && trace.type === 'waterfall') { + if(d[0]._distinct && isWaterfall) { var cont = d[0].trace[d[0].dir].marker; d[0].mc = cont.color; d[0].mlw = cont.line.width; @@ -300,7 +301,7 @@ module.exports = function style(s, gd, legend) { } var ptsData = []; - if(trace.visible && trace.type === 'waterfall') { + if(trace.visible && isWaterfall) { ptsData = d[0].hasTotals ? [['increasing', 'M-6,-6V6H0Z'], ['totals', 'M6,6H0L-6,-6H-0Z'], ['decreasing', 'M6,6V-6H0Z']] : [['increasing', 'M-6,-6V6H6Z'], ['decreasing', 'M6,6V-6H-6Z']]; From a9c899b02597408eb45fa39d54f3e51c84a55722 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 14:58:57 -0400 Subject: [PATCH 22/28] unified hoverlabel: return early if nothing is hovered on --- src/components/fx/hover.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 41e6e90696a..52d5e3f63b9 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -931,6 +931,9 @@ function createHoverText(hoverData, opts, gd) { // similarly to compare mode, we remove the "close but not quite together" points if((t0 !== undefined) && (c0.distance <= opts.hoverdistance)) hoverData = filterClosePoints(hoverData); + // Return early if nothing is hovered on + if(hoverData.length === 0) return; + // mock legend var mockLayoutIn = { showlegend: true, @@ -979,9 +982,6 @@ function createHoverText(hoverData, opts, gd) { } legendOpts.layer = container; - // Return early if nothing is hovered on - if(legendOpts.entries.length === 0) return; - // Draw unified hover label legendDraw(gd, legendOpts); From 06ad9a714e65b852fa0bf6a0e6114a3223629e11 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 16:37:17 -0400 Subject: [PATCH 23/28] unified hoverlabel: swap hovermode in orientationaxes block --- src/plot_api/plot_api.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index ef2d354966c..9495caa45fe 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1737,6 +1737,10 @@ function _restyle(gd, aobj, traces) { hovermode.set('y'); } else if(hovermode.get() === 'y') { hovermode.set('x'); + } else if(hovermode.get() === 'x unified') { + hovermode.set('y unified'); + } else if(hovermode.get() === 'y unified') { + hovermode.set('x unified'); } } From 53c571489027c0d0cfcd6901842c72440ab7bda0 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 17:12:28 -0400 Subject: [PATCH 24/28] unified hoverlabel: rank points based on trace's index --- src/components/fx/hover.js | 1 + test/jasmine/tests/hover_label_test.js | 27 ++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 52d5e3f63b9..8ec41867856 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -980,6 +980,7 @@ function createHoverText(hoverData, opts, gd) { legendOpts.entries.push([pt]); } + legendOpts.entries.sort(function(a, b) { return a[0].trace.index - b[0].trace.index;}); legendOpts.layer = container; // Draw unified hover label diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index ccf6d403d53..19a51a7832b 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3779,11 +3779,11 @@ describe('hovermode: (x|y)unified', function() { assertLabel({title: '3', items: [ 'trace 0 : 2', - 'trace 3 : 2', 'trace 1 : median: 1', - 'trace 4 : 1', + 'trace 3 : 2', 'trace 2 : 2', - 'trace 5 : 2' + 'trace 5 : 2', + 'trace 4 : 1' ]}); }) .catch(failTest) @@ -3812,7 +3812,7 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); - it('should order items in the same way as the legend', function(done) { + it('should have the same traceorder as the legend', function(done) { var mock = require('@mocks/stacked_area.json'); var mockCopy = Lib.extendDeep({}, mock); mockCopy.layout.hovermode = 'x unified'; @@ -3833,6 +3833,25 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); + it('should order items based on trace index as in the legend', function(done) { + var mock = require('@mocks/29.json'); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.hovermode = 'x unified'; + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, {xpx: 400, ypx: 400}); + + assertLabel({title: 'Apr 13, 2014, 09:51:36', items: [ + 'Outdoor (wun... : 63', + '1st Floor (N... : (Apr 13, 2014, 09:51:34, 69.5)', + '2nd Floor (R... : (Apr 13, 2014, 09:51:33, 69.125)', + 'Attic (Ardui... : (Apr 13, 2014, 09:51:37, 68.68)' + ]}); + }) + .catch(failTest) + .then(done); + }); + it('should work for finance traces', function(done) { var mockOhlc = require('@mocks/finance_multicategory.json'); var mockCopy = Lib.extendDeep({}, mockOhlc); From dd86a60661502a4adc9c4da8da2db19db7df135c Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 11 Mar 2020 18:08:11 -0400 Subject: [PATCH 25/28] unified hoverlabel: fix test --- test/jasmine/tests/hover_label_test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 19a51a7832b..5ae0084e391 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3839,13 +3839,13 @@ describe('hovermode: (x|y)unified', function() { mockCopy.layout.hovermode = 'x unified'; Plotly.newPlot(gd, mockCopy) .then(function(gd) { - _hover(gd, {xpx: 400, ypx: 400}); + _hover(gd, {curveNumber: 0}); - assertLabel({title: 'Apr 13, 2014, 09:51:36', items: [ - 'Outdoor (wun... : 63', - '1st Floor (N... : (Apr 13, 2014, 09:51:34, 69.5)', - '2nd Floor (R... : (Apr 13, 2014, 09:51:33, 69.125)', - 'Attic (Ardui... : (Apr 13, 2014, 09:51:37, 68.68)' + assertLabel({title: 'Apr 13, 2014, 15:21:11', items: [ + 'Outdoor (wun... : (Apr 13, 2014, 15:26:12, 69.4)', + '1st Floor (N... : (Apr 13, 2014, 15:21:15, 74.8)', + '2nd Floor (R... : 73.625', + 'Attic (Ardui... : (Apr 13, 2014, 15:26:34, 98.49)' ]}); }) .catch(failTest) From a857d71dc53db54a82ea64e6944c66aa9767c816 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 13 Mar 2020 13:02:12 -0400 Subject: [PATCH 26/28] unified hoverlabel: update description --- src/components/fx/layout_attributes.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index c855590b867..8cebf5c633b 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -61,6 +61,16 @@ module.exports = { editType: 'modebar', description: [ 'Determines the mode of hover interactions.', + 'If `closest`, a single hoverlabel will appear', + 'for the closest point within the `hoverdistance`.', + 'If `x` (or `y`), multiple hoverlabels will appear for multiple points', + 'at the closest `x`- (or `y`-) coordinate within the `hoverdistance`,', + 'with the caveat that no more than one hoverlabel will appear per trace.', + 'If `x unified` (or `y unified`), a single hoverlabel will appear', + 'multiple points at the closest x- (or y-) coordinate within the `hoverdistance`', + 'with the caveat that no more than one hoverlabel will appear per trace.', + 'In this mode, spikelines are enabled by default perpendicular to the specified axis.', + 'If false, hover interactions are disabled.', 'If `clickmode` includes the *select* flag,', '`hovermode` defaults to *closest*.', 'If `clickmode` lacks the *select* flag,', From 05188d6db82f77b977a5f50bf44a9fb57cfb9d0b Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 13 Mar 2020 13:26:14 -0400 Subject: [PATCH 27/28] unified hoverlabel: fix description --- src/components/fx/layout_attributes.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index 8cebf5c633b..64935ed59fb 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -61,12 +61,12 @@ module.exports = { editType: 'modebar', description: [ 'Determines the mode of hover interactions.', - 'If `closest`, a single hoverlabel will appear', - 'for the closest point within the `hoverdistance`.', - 'If `x` (or `y`), multiple hoverlabels will appear for multiple points', - 'at the closest `x`- (or `y`-) coordinate within the `hoverdistance`,', + 'If *closest*, a single hoverlabel will appear', + 'for the *closest* point within the `hoverdistance`.', + 'If *x* (or *y*), multiple hoverlabels will appear for multiple points', + 'at the *closest* x- (or y-) coordinate within the `hoverdistance`,', 'with the caveat that no more than one hoverlabel will appear per trace.', - 'If `x unified` (or `y unified`), a single hoverlabel will appear', + 'If *x unified* (or *y unified*), a single hoverlabel will appear', 'multiple points at the closest x- (or y-) coordinate within the `hoverdistance`', 'with the caveat that no more than one hoverlabel will appear per trace.', 'In this mode, spikelines are enabled by default perpendicular to the specified axis.', From af4a07c9bbe70ade135a733595321f8a8de7c184 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Fri, 13 Mar 2020 15:58:35 -0400 Subject: [PATCH 28/28] unified hoverlabel: remove modebar hover buttons when activated --- src/components/modebar/manage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index abf97f66944..884a3c2c3ad 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -85,6 +85,7 @@ function getButtonGroups(gd) { var hasPolar = fullLayout._has('polar'); var hasSankey = fullLayout._has('sankey'); var allAxesFixed = areAllAxesFixed(fullLayout); + var hasUnifiedHoverLabel = ['x unified', 'y unified'].indexOf(fullLayout.hovermode) !== -1; var groups = []; @@ -146,7 +147,7 @@ function getButtonGroups(gd) { if(hasCartesian) { hoverGroup = ['toggleSpikelines', 'hoverClosestCartesian', 'hoverCompareCartesian']; } - if(hasNoHover(fullData)) { + if(hasNoHover(fullData) || hasUnifiedHoverLabel) { hoverGroup = []; }