diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index c30114408d4..45617636789 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -989,9 +989,10 @@ function createHoverText(hoverData, opts, gd) { var mockLayoutIn = { showlegend: true, legend: { - title: {text: t0, font: fullLayout.font}, - font: fullLayout.font, - bgcolor: fullLayout.paper_bgcolor, + title: {text: t0, font: fullLayout.hoverlabel.font}, + font: fullLayout.hoverlabel.font, + bgcolor: fullLayout.hoverlabel.bgcolor, + bordercolor: fullLayout.hoverlabel.bordercolor, borderwidth: 1, tracegroupgap: 7, traceorder: fullLayout.legend ? fullLayout.legend.traceorder : undefined, diff --git a/src/components/fx/hoverlabel_defaults.js b/src/components/fx/hoverlabel_defaults.js index 55305b5fb0f..22f80c5fced 100644 --- a/src/components/fx/hoverlabel_defaults.js +++ b/src/components/fx/hoverlabel_defaults.js @@ -9,10 +9,33 @@ 'use strict'; var Lib = require('../../lib'); +var Color = require('../color'); +var isUnifiedHover = require('./helpers').isUnifiedHover; module.exports = function handleHoverLabelDefaults(contIn, contOut, coerce, opts) { opts = opts || {}; + function inheritFontAttr(attr) { + if(!opts.font[attr]) { + opts.font[attr] = contOut.legend ? contOut.legend.font[attr] : contOut.font[attr]; + } + } + + // In unified hover, inherit from layout.legend if available or layout + if(contOut && isUnifiedHover(contOut.hovermode)) { + if(!opts.font) opts.font = {}; + inheritFontAttr('size'); + inheritFontAttr('family'); + inheritFontAttr('color'); + + if(contOut.legend) { + if(!opts.bgcolor) opts.bgcolor = Color.combine(contOut.legend.bgcolor, contOut.paper_bgcolor); + if(!opts.bordercolor) opts.bordercolor = contOut.legend.bordercolor; + } else { + if(!opts.bgcolor) opts.bgcolor = contOut.paper_bgcolor; + } + } + coerce('hoverlabel.bgcolor', opts.bgcolor); coerce('hoverlabel.bordercolor', opts.bordercolor); coerce('hoverlabel.namelength', opts.namelength); diff --git a/src/components/fx/layout_defaults.js b/src/components/fx/layout_defaults.js index 80755253736..15d6a7f11f6 100644 --- a/src/components/fx/layout_defaults.js +++ b/src/components/fx/layout_defaults.js @@ -12,6 +12,7 @@ var Lib = require('../../lib'); var isUnifiedHover = require('./helpers').isUnifiedHover; var layoutAttributes = require('./layout_attributes'); var handleHoverModeDefaults = require('./hovermode_defaults'); +var handleHoverLabelDefaults = require('./hoverlabel_defaults'); module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { function coerce(attr, dflt) { @@ -40,4 +41,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { )) { layoutOut.dragmode = 'pan'; } + + handleHoverLabelDefaults(layoutIn, layoutOut, coerce); }; diff --git a/src/core.js b/src/core.js index eb45036dbd0..47bb61ee918 100644 --- a/src/core.js +++ b/src/core.js @@ -42,8 +42,8 @@ register(require('./traces/scatter')); // register all registrable components modules register([ - require('./components/fx'), require('./components/legend'), + require('./components/fx'), // fx needs to come after legend require('./components/annotations'), require('./components/annotations3d'), require('./components/shapes'), diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 342301787d9..b599afb25b0 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -3988,14 +3988,18 @@ describe('hovermode: (x|y)unified', function() { Lib.clearThrottle(); } + function getHoverLabel() { + var hoverLayer = d3.select('g.hoverlayer'); + return hoverLayer.select('g.legend'); + } + function assertElementCount(selector, size) { var g = d3.selectAll(selector); expect(g.size()).toBe(size); } function assertLabel(expectation) { - var hoverLayer = d3.select('g.hoverlayer'); - var hover = hoverLayer.select('g.legend'); + var hover = getHoverLabel(); var title = hover.select('text.legendtitletext'); var traces = hover.selectAll('g.traces'); @@ -4010,16 +4014,15 @@ describe('hovermode: (x|y)unified', function() { }); } - function assertBgcolor(color) { - var hoverLayer = d3.select('g.hoverlayer'); - var hover = hoverLayer.select('g.legend'); + function assertRectColor(color, bordercolor) { + var hover = getHoverLabel(); var bg = hover.select('rect.bg'); - expect(bg.node().style.fill).toBe(color); + if(color) expect(bg.node().style.fill).toBe(color); + if(bordercolor) expect(bg.node().style.stroke).toBe(bordercolor); } function assertSymbol(exp) { - var hoverLayer = d3.select('g.hoverlayer'); - var hover = hoverLayer.select('g.legend'); + var hover = getHoverLabel(); var traces = hover.selectAll('g.traces'); expect(traces.size()).toBe(exp.length); @@ -4034,6 +4037,17 @@ describe('hovermode: (x|y)unified', function() { }); } + function assertFont(fontFamily, fontSize, fontColor) { + var hover = getHoverLabel(); + var text = hover.select('text.legendtext'); + var node = text.node(); + + var textStyle = window.getComputedStyle(node); + expect(textStyle.fontFamily.split(',')[0]).toBe(fontFamily, 'wrong font family'); + expect(textStyle.fontSize).toBe(fontSize, 'wrong font size'); + expect(textStyle.fill).toBe(fontColor, 'wrong font 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) { @@ -4316,15 +4330,149 @@ describe('hovermode: (x|y)unified', function() { .then(done); }); - it('label should have color of paper_bgcolor', function(done) { + it('label should have bgcolor/bordercolor from hoverlabel or legend or paper_bgcolor', function(done) { + var mockCopy = Lib.extendDeep({}, mock); + var bgcolor = [ + 'rgb(10, 10, 10)', + 'rgb(20, 20, 20)', + 'rgb(30, 30, 30)', + 'rgb(40, 40, 40)' + ]; + + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertRectColor('rgb(255, 255, 255)', 'rgb(68, 68, 68)'); + + // Set paper_bgcolor + return Plotly.relayout(gd, 'paper_bgcolor', bgcolor[0]); + }) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertRectColor(bgcolor[0]); + + // Set legend.bgcolor which should win over paper_bgcolor + return Plotly.relayout(gd, { + 'showlegend': true, + 'legend.bgcolor': bgcolor[1], + 'legend.bordercolor': bgcolor[1] + }); + }) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertRectColor(bgcolor[1], bgcolor[1]); + + // Set hoverlabel.bgcolor which should win over legend.bgcolor + return Plotly.relayout(gd, { + 'hoverlabel.bgcolor': bgcolor[2], + 'hoverlabel.bordercolor': bgcolor[2] + }); + }) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertRectColor(bgcolor[2], bgcolor[2]); + + // Check that a legend.bgcolor defined in template works + delete mockCopy.layout; + mockCopy.layout = { + hovermode: 'x unified', + template: { layout: { legend: { bgcolor: bgcolor[1], bordercolor: bgcolor[1] } } } + }; + + return Plotly.newPlot(gd, mockCopy); + }) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertRectColor(bgcolor[1], bgcolor[1]); + + // Check that a hoverlabel.bgcolor defined in template wins + delete mockCopy.layout; + mockCopy.layout = { + hovermode: 'x unified', + template: { layout: { hoverlabel: { bgcolor: bgcolor[3], bordercolor: bgcolor[3] } } }, + legend: { bgcolor: bgcolor[1], bordercolor: bgcolor[1] } + }; + + return Plotly.newPlot(gd, mockCopy); + }) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertRectColor(bgcolor[3], bgcolor[3]); + }) + .catch(failTest) + .then(done); + }); + + it('should use hoverlabel.font or legend.font or layout.font', function(done) { var mockCopy = Lib.extendDeep({}, mock); - var bgcolor = 'rgb(15, 200, 85)'; - mockCopy.layout.paper_bgcolor = bgcolor; + + // Set layout.font + mockCopy.layout.font = {size: 20, family: 'Mono', color: 'rgb(10, 10, 10)'}; Plotly.newPlot(gd, mockCopy) .then(function(gd) { + _hover(gd, { xval: 3}); + + assertFont('Mono', '20px', 'rgb(10, 10, 10)'); + + // Set legend.font which should win over layout font + return Plotly.relayout(gd, { + 'showlegend': true, + 'legend.font.size': 15, + 'legend.font.family': 'Helvetica', + 'legend.font.color': 'rgb(20, 20, 20)' + }); + }) + .then(function(gd) { + _hover(gd, { xval: 3 }); + + assertFont('Helvetica', '15px', 'rgb(20, 20, 20)'); + + // Set hoverlabel.font which should win over legend.font + return Plotly.relayout(gd, { + 'hoverlabel.font.size': 22, + 'hoverlabel.font.family': 'Arial', + 'hoverlabel.font.color': 'rgb(30, 30, 30)' + }); + }) + .then(function() { + _hover(gd, { xval: 3 }); + + assertFont('Arial', '22px', 'rgb(30, 30, 30)'); + + // Check that a legend.font defined in template wins + delete mockCopy.layout; + mockCopy.layout = { + hovermode: 'x unified', + template: { layout: { legend: {font: {size: 5, family: 'Mono', color: 'rgb(5, 5, 5)'}}}}, + }; + + return Plotly.newPlot(gd, mockCopy); + }) + .then(function() { + _hover(gd, { xval: 3 }); + + assertFont('Mono', '5px', 'rgb(5, 5, 5)'); + + // Finally, check that a hoverlabel.font defined in template wins + delete mockCopy.layout; + mockCopy.layout = { + hovermode: 'x unified', + template: { layout: { hoverlabel: { font: {family: 'Mono', size: 30, color: 'red'}}}}, + legend: {font: {size: 20, family: 'Mono', color: 'rgb(10, 10, 10)'}} + }; + + return Plotly.newPlot(gd, mockCopy); + }) + .then(function() { _hover(gd, { xval: 3 }); - assertBgcolor(bgcolor); + assertFont('Mono', '30px', 'rgb(255, 0, 0)'); }) .catch(failTest) .then(done);