Skip to content

Commit

Permalink
Merge pull request #5690 from plotly/other-hovertemplate
Browse files Browse the repository at this point in the history
Implement (x|y)other hovertemplate to format differing positions in compare and unified modes
  • Loading branch information
archmoj authored Jun 1, 2021
2 parents c07a8e0 + a6da451 commit 18e512c
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 33 deletions.
23 changes: 15 additions & 8 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -1270,14 +1270,17 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) {
name = plainText(d.name, d.nameLength);
}

var h0 = hovermode.charAt(0);
var h1 = h0 === 'x' ? 'y' : 'x';

if(d.zLabel !== undefined) {
if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '<br>';
if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '<br>';
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(showCommonLabel && d[h0 + 'Label'] === t0) {
text = d[h1 + 'Label'] || '';
} else if(d.xLabel === undefined) {
if(d.yLabel !== undefined && d.trace.type !== 'scattercarpet') {
text = d.yLabel;
Expand Down Expand Up @@ -1306,16 +1309,20 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) {
}

// hovertemplate
var d3locale = fullLayout._d3locale;
var hovertemplate = d.hovertemplate || false;
var hovertemplateLabels = d.hovertemplateLabels || d;
var eventData = d.eventData[0] || {};
if(hovertemplate) {
var labels = d.hovertemplateLabels || d;

if(d[h0 + 'Label'] !== t0) {
labels[h0 + 'other'] = labels[h0 + 'Val'];
labels[h0 + 'otherLabel'] = labels[h0 + 'Label'];
}

text = Lib.hovertemplateString(
hovertemplate,
hovertemplateLabels,
d3locale,
eventData,
labels,
fullLayout._d3locale,
d.eventData[0] || {},
d.trace._meta
);

Expand Down
65 changes: 51 additions & 14 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1043,21 +1043,50 @@ function templateFormatString(string, labels, d3locale) {
// just in case it speeds things up *slightly*:
var getterCache = {};

return string.replace(lib.TEMPLATE_STRING_REGEX, function(match, key, format) {
var obj, value, i;
for(i = 3; i < args.length; i++) {
obj = args[i];
if(!obj) continue;
if(obj.hasOwnProperty(key)) {
value = obj[key];
break;
}
return string.replace(lib.TEMPLATE_STRING_REGEX, function(match, rawKey, format) {
var isOther =
rawKey === 'xother' ||
rawKey === 'yother';

var isSpaceOther =
rawKey === '_xother' ||
rawKey === '_yother';

var isSpaceOtherSpace =
rawKey === '_xother_' ||
rawKey === '_yother_';

var isOtherSpace =
rawKey === 'xother_' ||
rawKey === 'yother_';

if(!SIMPLE_PROPERTY_REGEX.test(key)) {
value = getterCache[key] || lib.nestedProperty(obj, key).get();
if(value) getterCache[key] = value;
var hasOther = isOther || isSpaceOther || isOtherSpace || isSpaceOtherSpace;

var key = rawKey;
if(isSpaceOther || isSpaceOtherSpace) key = key.substring(1);
if(isOtherSpace || isSpaceOtherSpace) key = key.substring(0, key.length - 1);

var value;
if(hasOther) {
value = labels[key];
if(value === undefined) return '';
} else {
var obj, i;
for(i = 3; i < args.length; i++) {
obj = args[i];
if(!obj) continue;
if(obj.hasOwnProperty(key)) {
value = obj[key];
break;
}

if(!SIMPLE_PROPERTY_REGEX.test(key)) {
value = lib.nestedProperty(obj, key).get();
value = getterCache[key] || lib.nestedProperty(obj, key).get();
if(value) getterCache[key] = value;
}
if(value !== undefined) break;
}
if(value !== undefined) break;
}

if(value === undefined && opts) {
Expand Down Expand Up @@ -1087,8 +1116,16 @@ function templateFormatString(string, labels, d3locale) {
value = lib.formatDate(ms, format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''), false, fmt);
}
} else {
if(labels.hasOwnProperty(key + 'Label')) value = labels[key + 'Label'];
var keyLabel = key + 'Label';
if(labels.hasOwnProperty(keyLabel)) value = labels[keyLabel];
}

if(hasOther) {
value = '(' + value + ')';
if(isSpaceOther || isSpaceOtherSpace) value = ' ' + value;
if(isOtherSpace || isSpaceOtherSpace) value = value + ' ';
}

return value;
});
}
Expand Down
31 changes: 20 additions & 11 deletions src/plots/template_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ var docs = require('../constants/docs');
var FORMAT_LINK = docs.FORMAT_LINK;
var DATE_FORMAT_LINK = docs.DATE_FORMAT_LINK;

var templateFormatStringDescription = [
'Variables are inserted using %{variable}, for example "y: %{y}".',
'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".',
FORMAT_LINK,
'for details on the formatting syntax.',
'Dates are formatted using d3-time-format\'s syntax %{variable|d3-time-format}, for example "Day: %{2019-01-01|%A}".',
DATE_FORMAT_LINK,
'for details on the date formatting syntax.'
].join(' ');
function templateFormatStringDescription(opts) {
var supportOther = opts && opts.supportOther;

return [
'Variables are inserted using %{variable},',
'for example "y: %{y}"' + (
supportOther ?
' as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown.' :
'.'
),
'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".',
FORMAT_LINK,
'for details on the formatting syntax.',
'Dates are formatted using d3-time-format\'s syntax %{variable|d3-time-format}, for example "Day: %{2019-01-01|%A}".',
DATE_FORMAT_LINK,
'for details on the date formatting syntax.'
].join(' ');
}

function describeVariables(extra) {
var descPart = extra.description ? ' ' + extra.description : '';
Expand Down Expand Up @@ -45,7 +54,7 @@ exports.hovertemplateAttrs = function(opts, extra) {
description: [
'Template string used for rendering the information that appear on hover box.',
'Note that this will override `hoverinfo`.',
templateFormatStringDescription,
templateFormatStringDescription({supportOther: true}),
'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.',
'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.',
descPart,
Expand Down Expand Up @@ -74,7 +83,7 @@ exports.texttemplateAttrs = function(opts, extra) {
description: [
'Template string used for rendering the information text that appear on points.',
'Note that this will override `textinfo`.',
templateFormatStringDescription,
templateFormatStringDescription(),
'Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.',
descPart
].join(' ')
Expand Down
70 changes: 70 additions & 0 deletions test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4831,6 +4831,76 @@ describe('hovermode: (x|y)unified', function() {
.then(done, done.fail);
});

it('should format differing position using *xother* `hovertemplate` and in respect to `xhoverformat`', function(done) {
Plotly.newPlot(gd, [{
type: 'bar',
hovertemplate: 'y(_x):%{y}%{_xother:.2f}',
x: [0, 1.001],
y: [2, 1]
}, {
x: [0, 0.749],
y: [1, 2]
}, {
hovertemplate: '(x)y:%{xother}%{y}',
xhoverformat: '.1f',
x: [0, 1.251],
y: [2, 3]
}, {
hovertemplate: '(x_)y:%{xother_}%{y}',
xhoverformat: '.2f',
x: [0, 1.351],
y: [3, 4]
}, {
hovertemplate: '(_x_)y:%{_xother_}%{y}',
xhoverformat: '.3f',
x: [0, 1.451],
y: [4, 5]
}], {
hoverdistance: -1,
hovermode: 'x unified',
showlegend: false,
width: 500,
height: 500,
margin: {
t: 50,
b: 50,
l: 50,
r: 50
}
})
.then(function() {
_hover(gd, { xpx: 100, ypx: 200 });
assertLabel({title: '0.000', items: [
'trace 0 : y(_x):2 (0.00)',
'trace 1 : (0, 1)',
'trace 2 : (x)y:(0.0)2',
'trace 3 : (x_)y:(0.00) 3',
'trace 4 : (_x_)y:4',
]});
})
.then(function() {
_hover(gd, { xpx: 250, ypx: 200 });
assertLabel({title: '0.749', items: [
'trace 0 : y(_x):1 (1.00)',
'trace 1 : 2',
'trace 2 : (x)y:(1.3)3',
'trace 3 : (x_)y:(1.35) 4',
'trace 4 : (_x_)y: (1.451) 5',
]});
})
.then(function() {
_hover(gd, { xpx: 350, ypx: 200 });
assertLabel({title: '1.35', items: [
'trace 0 : y(_x):1 (1.00)',
'trace 1 : (0.749, 2)',
'trace 2 : (x)y:(1.3)3',
'trace 3 : (x_)y:4',
'trace 4 : (_x_)y: (1.451) 5',
]});
})
.then(done, done.fail);
});

it('should display hover for two high-res scatter at different various intervals', function(done) {
var x1 = [];
var y1 = [];
Expand Down

0 comments on commit 18e512c

Please sign in to comment.