diff --git a/draftlogs/6040_add.md b/draftlogs/6040_add.md new file mode 100644 index 00000000000..452e793c7b8 --- /dev/null +++ b/draftlogs/6040_add.md @@ -0,0 +1 @@ + - Implement legend.grouptitlefont and hoverlabel.grouptitlefont [[#6040](https://github.com/plotly/plotly.js/pull/6040)] diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 76b7bbd1f8a..0643c004bcd 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1103,7 +1103,9 @@ function createHoverText(hoverData, opts) { orientation: 'v' } }; - var mockLayoutOut = {}; + var mockLayoutOut = { + font: font + }; legendSupplyDefaults(mockLayoutIn, mockLayoutOut, gd._fullData); var mockLegend = mockLayoutOut.legend; @@ -1144,7 +1146,8 @@ function createHoverText(hoverData, opts) { // Draw unified hover label mockLegend._inHover = true; - mockLegend._groupTitleFont = font; + mockLegend._groupTitleFont = hoverlabel.grouptitlefont; + legendDraw(gd, mockLegend); // Position the hover diff --git a/src/components/fx/hoverlabel_defaults.js b/src/components/fx/hoverlabel_defaults.js index c0e62bbd9d4..6e8627e24a7 100644 --- a/src/components/fx/hoverlabel_defaults.js +++ b/src/components/fx/hoverlabel_defaults.js @@ -7,9 +7,11 @@ var isUnifiedHover = require('./helpers').isUnifiedHover; module.exports = function handleHoverLabelDefaults(contIn, contOut, coerce, opts) { opts = opts || {}; + var hasLegend = contOut.legend; + function inheritFontAttr(attr) { if(!opts.font[attr]) { - opts.font[attr] = contOut.legend ? contOut.legend.font[attr] : contOut.font[attr]; + opts.font[attr] = hasLegend ? contOut.legend.font[attr] : contOut.font[attr]; } } @@ -20,7 +22,7 @@ module.exports = function handleHoverLabelDefaults(contIn, contOut, coerce, opts inheritFontAttr('family'); inheritFontAttr('color'); - if(contOut.legend) { + if(hasLegend) { if(!opts.bgcolor) opts.bgcolor = Color.combine(contOut.legend.bgcolor, contOut.paper_bgcolor); if(!opts.bordercolor) opts.bordercolor = contOut.legend.bordercolor; } else { diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index e2deaa1ed3f..af5ce341e21 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -2,12 +2,14 @@ var constants = require('./constants'); -var fontAttrs = require('../../plots/font_attributes')({ +var fontAttrs = require('../../plots/font_attributes'); + +var font = fontAttrs({ editType: 'none', description: 'Sets the default hover label font used by all traces on the graph.' }); -fontAttrs.family.dflt = constants.HOVERFONT; -fontAttrs.size.dflt = constants.HOVERFONTSIZE; +font.family.dflt = constants.HOVERFONT; +font.size.dflt = constants.HOVERFONTSIZE; module.exports = { clickmode: { @@ -118,7 +120,14 @@ module.exports = { 'Sets the border color of all hover labels on graph.' ].join(' ') }, - font: fontAttrs, + font: font, + grouptitlefont: fontAttrs({ + editType: 'none', + description: [ + 'Sets the font for group titles in hover (unified modes).', + 'Defaults to `hoverlabel.font`.' + ].join(' ') + }), align: { valType: 'enumerated', values: ['left', 'right', 'auto'], @@ -143,6 +152,7 @@ module.exports = { '`namelength - 3` characters and add an ellipsis.' ].join(' ') }, + editType: 'none' }, selectdirection: { diff --git a/src/components/fx/layout_defaults.js b/src/components/fx/layout_defaults.js index 655d708a249..304e6caf11f 100644 --- a/src/components/fx/layout_defaults.js +++ b/src/components/fx/layout_defaults.js @@ -34,4 +34,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { } handleHoverLabelDefaults(layoutIn, layoutOut, coerce); + + Lib.coerceFont(coerce, 'hoverlabel.grouptitlefont', layoutOut.hoverlabel.font); }; diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index e5713595d39..f294d2471f0 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -30,6 +30,13 @@ module.exports = { editType: 'legend', description: 'Sets the font used to text the legend items.' }), + grouptitlefont: fontAttrs({ + editType: 'legend', + description: [ + 'Sets the font for group titles in legend.', + 'Defaults to `legend.font` with its size increased about 10%.' + ].join(' ') + }), orientation: { valType: 'enumerated', values: ['v', 'h'], diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index 7862a631956..70afc29ab33 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -4,6 +4,7 @@ var Registry = require('../../registry'); var Lib = require('../../lib'); var Template = require('../../plot_api/plot_template'); +var plotsAttrs = require('../../plots/attributes'); var attributes = require('./attributes'); var basePlotLayoutAttributes = require('../../plots/layout_attributes'); var helpers = require('./helpers'); @@ -11,13 +12,30 @@ var helpers = require('./helpers'); module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { var containerIn = layoutIn.legend || {}; + var containerOut = Template.newContainer(layoutOut, 'legend'); + + function coerce(attr, dflt) { + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); + } + + var trace; + var traceCoerce = function(attr, dflt) { + var traceIn = trace._input; + var traceOut = trace; + return Lib.coerce(traceIn, traceOut, plotsAttrs, attr, dflt); + }; + + var globalFont = layoutOut.font || {}; + var grouptitlefont = Lib.coerceFont(coerce, 'grouptitlefont', Lib.extendFlat({}, globalFont, { + size: Math.round(globalFont.size * 1.1) + })); var legendTraceCount = 0; var legendReallyHasATrace = false; var defaultOrder = 'normal'; for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i]; + trace = fullData[i]; if(!trace.visible) continue; @@ -44,6 +62,8 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { legendTraceCount++; } } + + Lib.coerceFont(traceCoerce, 'legendgrouptitle.font', grouptitlefont); } if((Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') || @@ -62,13 +82,10 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { basePlotLayoutAttributes, 'showlegend', legendReallyHasATrace && legendTraceCount > 1); - if(showLegend === false && !containerIn.uirevision) return; - - var containerOut = Template.newContainer(layoutOut, 'legend'); + // delete legend + if(showLegend === false) layoutOut.legend = undefined; - function coerce(attr, dflt) { - return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); - } + if(showLegend === false && !containerIn.uirevision) return; coerce('uirevision', layoutOut.uirevision); diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 1a9386a3915..15caf485e5f 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1719,7 +1719,7 @@ function cleanDeprecatedAttributeKeys(aobj) { if((key === 'title' || oldAxisTitleRegex.test(key) || colorbarRegex.test(key)) && (typeof value === 'string' || typeof value === 'number')) { replace(key, key.replace('title', 'title.text')); - } else if(key.indexOf('titlefont') > -1) { + } else if(key.indexOf('titlefont') > -1 && key.indexOf('grouptitlefont') === -1) { replace(key, key.replace('titlefont', 'title.font')); } else if(key.indexOf('titleposition') > -1) { replace(key, key.replace('titleposition', 'title.position')); diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index 100503d9c21..ee955e5bfc7 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -309,6 +309,7 @@ module.exports = { 'c) One trace is explicitly given with `showlegend: true`.' ].join(' ') }, + colorway: { valType: 'colorlist', dflt: colorAttrs.defaults, diff --git a/src/plots/plots.js b/src/plots/plots.js index d4ec300d719..cda76cf8f37 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1321,13 +1321,7 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac ); coerce('legendgroup'); - var titleText = coerce('legendgrouptitle.text'); - if(titleText) { - Lib.coerceFont(coerce, 'legendgrouptitle.font', Lib.extendFlat({}, layout.font, { - size: Math.round(layout.font.size * 1.1) // default to larger font size - })); - } - + coerce('legendgrouptitle.text'); coerce('legendrank'); traceOut._dfltShowLegend = true; @@ -1475,16 +1469,14 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) { coerce('autotypenumbers'); - var globalFont = Lib.coerceFont(coerce, 'font'); - - coerce('title.text', layoutOut._dfltTitle.plot); + var font = Lib.coerceFont(coerce, 'font'); + var fontSize = font.size; - Lib.coerceFont(coerce, 'title.font', { - family: globalFont.family, - size: Math.round(globalFont.size * 1.4), - color: globalFont.color - }); + Lib.coerceFont(coerce, 'title.font', Lib.extendFlat({}, font, { + size: Math.round(fontSize * 1.4) + })); + coerce('title.text', layoutOut._dfltTitle.plot); coerce('title.xref'); coerce('title.yref'); coerce('title.x'); diff --git a/test/image/baselines/legendrank.png b/test/image/baselines/legendrank.png index a25a14c5d87..08971ed4ccb 100644 Binary files a/test/image/baselines/legendrank.png and b/test/image/baselines/legendrank.png differ diff --git a/test/image/mocks/legendrank.json b/test/image/mocks/legendrank.json index d7d755a648a..608600caa66 100644 --- a/test/image/mocks/legendrank.json +++ b/test/image/mocks/legendrank.json @@ -37,6 +37,20 @@ "text": "legendrank" }, "hovermode": "x unified", + "hoverlabel": { + "grouptitlefont": { + "family": "Raleway", + "color": "red", + "size": 16 + } + }, + "legend": { + "grouptitlefont": { + "family": "Times New Roman", + "color": "orange", + "size": 14 + } + }, "margin": { "t": 50, "b": 25 diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index c8995911866..0461344a3f1 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -6144,6 +6144,40 @@ describe('hovermode: (x|y)unified', function() { .then(done, done.fail); }); + it('should use hoverlabel.grouptitlefont for group titles', function(done) { + function assertFont(fontFamily, fontSize, fontColor) { + var hover = getHoverLabel(); + var traces = hover.selectAll('g.traces'); + + traces.each(function() { + var e = d3Select(this); + var text = e.select('text.legendtext'); + var node = text.node(); + var label = node.innerHTML; + if(label.indexOf('group') !== -1) { + 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'); + } + }); + } + + var mockCopy = Lib.extendDeep({}, groupTitlesMock); + + mockCopy.layout.hoverlabel = { + grouptitlefont: {size: 20, family: 'Mono', color: 'rgb(255, 127, 0)'} + }; + + Plotly.newPlot(gd, mockCopy) + .then(function(gd) { + _hover(gd, { xval: 0}); + + assertFont('Mono', '20px', 'rgb(255, 127, 0)'); + }) + .then(done, done.fail); + }); + it('should work with hovertemplate', function(done) { var mockCopy = Lib.extendDeep({}, mock); mockCopy.data[0].hovertemplate = 'hovertemplate: %{y:0.2f}'; diff --git a/test/plot-schema.json b/test/plot-schema.json index a49f77f14a5..d83b6b38ce2 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2494,6 +2494,27 @@ "valType": "number" } }, + "grouptitlefont": { + "color": { + "editType": "none", + "valType": "color" + }, + "description": "Sets the font for group titles in hover (unified modes). Defaults to `hoverlabel.font`.", + "editType": "none", + "family": { + "description": "HTML font family - the typeface that will be applied by the web browser. The web browser will only be able to apply a font if it is available on the system which it operates. Provide multiple font families, separated by commas, to indicate the preference in which to apply fonts if they aren't available on the system. The Chart Studio Cloud (at https://chart-studio.plotly.com or on-premise) generates images on a server, where only a select number of fonts are installed and supported. These include *Arial*, *Balto*, *Courier New*, *Droid Sans*,, *Droid Serif*, *Droid Sans Mono*, *Gravitas One*, *Old Standard TT*, *Open Sans*, *Overpass*, *PT Sans Narrow*, *Raleway*, *Times New Roman*.", + "editType": "none", + "noBlank": true, + "strict": true, + "valType": "string" + }, + "role": "object", + "size": { + "editType": "none", + "min": 1, + "valType": "number" + } + }, "namelength": { "description": "Sets the default length (in number of characters) of the trace name in the hover labels for all traces. -1 shows the whole name regardless of length. 0-3 shows the first 0-3 characters, and an integer >3 will show the whole name if it is less than that many characters, but if it is longer, will truncate to `namelength - 3` characters and add an ellipsis.", "dflt": 15, @@ -2693,6 +2714,27 @@ "togglegroup" ] }, + "grouptitlefont": { + "color": { + "editType": "legend", + "valType": "color" + }, + "description": "Sets the font for group titles in legend. Defaults to `legend.font` with its size increased about 10%.", + "editType": "legend", + "family": { + "description": "HTML font family - the typeface that will be applied by the web browser. The web browser will only be able to apply a font if it is available on the system which it operates. Provide multiple font families, separated by commas, to indicate the preference in which to apply fonts if they aren't available on the system. The Chart Studio Cloud (at https://chart-studio.plotly.com or on-premise) generates images on a server, where only a select number of fonts are installed and supported. These include *Arial*, *Balto*, *Courier New*, *Droid Sans*,, *Droid Serif*, *Droid Sans Mono*, *Gravitas One*, *Old Standard TT*, *Open Sans*, *Overpass*, *PT Sans Narrow*, *Raleway*, *Times New Roman*.", + "editType": "legend", + "noBlank": true, + "strict": true, + "valType": "string" + }, + "role": "object", + "size": { + "editType": "legend", + "min": 1, + "valType": "number" + } + }, "itemclick": { "description": "Determines the behavior on legend item click. *toggle* toggles the visibility of the item clicked on the graph. *toggleothers* makes the clicked item the sole visible item on the graph. *false* disables legend item click interactions.", "dflt": "toggle",