Skip to content

Commit 8deaed1

Browse files
committed
texttemplate: initial implementation
Add `textemplate` support to the following traces: - [x] pie - [x] sunburst - [x] funnelarea - [x] bar - [x] waterfall - [x] funnel - [x] scatter
1 parent 1bebca6 commit 8deaed1

34 files changed

+624
-64
lines changed

src/components/drawing/index.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -689,15 +689,20 @@ drawing.textPointStyle = function(s, trace, gd) {
689689
selectedTextColorFn = fns.selectedTextColorFn;
690690
}
691691

692+
var template = trace.texttemplate;
692693
s.each(function(d) {
693694
var p = d3.select(this);
694-
var text = Lib.extractOption(d, trace, 'tx', 'text');
695+
var text = Lib.extractOption(d, trace, template ? 'txt' : 'tx', template ? 'texttemplate' : 'text');
695696

696697
if(!text && text !== 0) {
697698
p.remove();
698699
return;
699700
}
700701

702+
if(template) {
703+
text = Lib.texttemplateString(text, {}, gd._fullLayout._d3locale, d, trace._meta || {});
704+
}
705+
701706
var pos = d.tp || trace.textposition;
702707
var fontSize = extracTextFontSize(d, trace);
703708
var fontColor = selectedTextColorFn ?
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright 2012-2019, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var FORMAT_LINK = require('../../constants/docs').FORMAT_LINK;
12+
13+
module.exports = function(opts, extra) {
14+
opts = opts || {};
15+
extra = extra || {};
16+
17+
var descPart = extra.description ? ' ' + extra.description : '';
18+
var keys = extra.keys || [];
19+
if(keys.length > 0) {
20+
var quotedKeys = [];
21+
for(var i = 0; i < keys.length; i++) {
22+
quotedKeys[i] = '`' + keys[i] + '`';
23+
}
24+
descPart = descPart + 'Finally, the template string has access to ';
25+
if(keys.length === 1) {
26+
descPart = 'variable ' + quotedKeys[0];
27+
} else {
28+
descPart = 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.';
29+
}
30+
}
31+
32+
var texttemplate = {
33+
valType: 'string',
34+
role: 'info',
35+
dflt: '',
36+
editType: opts.editType || 'calc',
37+
description: [
38+
'Template string used for rendering the information that appear on points.',
39+
'Note that this will override `textinfo`.',
40+
'Variables are inserted using %{variable}, for example "y: %{y}".',
41+
'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".',
42+
FORMAT_LINK,
43+
'for details on the formatting syntax.',
44+
'Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.',
45+
descPart
46+
].join(' ')
47+
};
48+
49+
if(opts.arrayOk !== false) {
50+
texttemplate.arrayOk = true;
51+
}
52+
return texttemplate;
53+
};

src/lib/index.js

+29-10
Original file line numberDiff line numberDiff line change
@@ -993,9 +993,25 @@ lib.templateString = function(string, obj) {
993993
});
994994
};
995995

996+
var hovertemplateWarnings = {
997+
max: 10,
998+
count: 0,
999+
name: 'hovertemplate'
1000+
};
1001+
lib.hovertemplateString = function() {
1002+
return lib.templateFormatString.apply(hovertemplateWarnings, arguments);
1003+
};
1004+
1005+
var texttemplateWarnings = {
1006+
max: 10,
1007+
count: 0,
1008+
name: 'texttemplate'
1009+
};
1010+
lib.texttemplateString = function() {
1011+
return lib.templateFormatString.apply(texttemplateWarnings, arguments);
1012+
};
1013+
9961014
var TEMPLATE_STRING_FORMAT_SEPARATOR = /^:/;
997-
var numberOfHoverTemplateWarnings = 0;
998-
var maximumNumberOfHoverTemplateWarnings = 10;
9991015
/**
10001016
* Substitute values from an object into a string and optionally formats them using d3-format,
10011017
* or fallback to associated labels.
@@ -1005,14 +1021,15 @@ var maximumNumberOfHoverTemplateWarnings = 10;
10051021
* Lib.hovertemplateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf'
10061022
* Lib.hovertemplateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00'
10071023
*
1008-
* @param {obj} d3 locale
10091024
* @param {string} input string containing %{...:...} template strings
10101025
* @param {obj} data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'}
1026+
* @param {obj} d3 locale
10111027
* @param {obj} data objects containing substitution values
10121028
*
10131029
* @return {string} templated string
10141030
*/
1015-
lib.hovertemplateString = function(string, labels, d3locale) {
1031+
lib.templateFormatString = function(string, labels, d3locale) {
1032+
var opts = this;
10161033
var args = arguments;
10171034
// Not all that useful, but cache nestedProperty instantiation
10181035
// just in case it speeds things up *slightly*:
@@ -1034,16 +1051,18 @@ lib.hovertemplateString = function(string, labels, d3locale) {
10341051
if(value !== undefined) break;
10351052
}
10361053

1037-
if(value === undefined) {
1038-
if(numberOfHoverTemplateWarnings < maximumNumberOfHoverTemplateWarnings) {
1039-
lib.warn('Variable \'' + key + '\' in hovertemplate could not be found!');
1054+
if(value === undefined && opts) {
1055+
if(opts.count < opts.max) {
1056+
lib.warn('Variable \'' + key + '\' in ' + opts.name + ' could not be found!');
10401057
value = match;
10411058
}
10421059

1043-
if(numberOfHoverTemplateWarnings === maximumNumberOfHoverTemplateWarnings) {
1044-
lib.warn('Too many hovertemplate warnings - additional warnings will be suppressed');
1060+
if(opts.count === opts.max) {
1061+
lib.warn('Too many ' + opts.name + ' warnings - additional warnings will be suppressed');
10451062
}
1046-
numberOfHoverTemplateWarnings++;
1063+
opts.count++;
1064+
1065+
return match;
10471066
}
10481067

10491068
if(format) {

src/traces/bar/attributes.js

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
var scatterAttrs = require('../scatter/attributes');
1212
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
13+
var texttemplateAttrs = require('../../components/fx/texttemplate_attributes');
1314
var colorScaleAttrs = require('../../components/colorscale/attributes');
1415
var fontAttrs = require('../../plots/font_attributes');
1516
var constants = require('./constants.js');
@@ -59,6 +60,9 @@ module.exports = {
5960
dy: scatterAttrs.dy,
6061

6162
text: scatterAttrs.text,
63+
texttemplate: texttemplateAttrs({editType: 'plot'}, {
64+
keys: constants.eventDataKeys
65+
}),
6266
hovertext: scatterAttrs.hovertext,
6367
hovertemplate: hovertemplateAttrs({}, {
6468
keys: constants.eventDataKeys

src/traces/bar/defaults.js

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
3636
coerce('width');
3737

3838
coerce('text');
39+
coerce('texttemplate');
3940
coerce('hovertext');
4041
coerce('hovertemplate');
4142

src/traces/bar/plot.js

+66-5
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts) {
226226
var trace = calcTrace[0].trace;
227227
var isHorizontal = (trace.orientation === 'h');
228228

229-
var text = getText(calcTrace, i, xa, ya);
229+
var text = getText(fullLayout, calcTrace, i, xa, ya);
230230
textPosition = getTextPosition(trace, i);
231231

232232
// compute text position
@@ -516,14 +516,17 @@ function getTransform(textX, textY, targetX, targetY, scale, rotation) {
516516
return transformTranslate + transformScale + transformRotate;
517517
}
518518

519-
function getText(calcTrace, index, xa, ya) {
519+
function getText(fullLayout, calcTrace, index, xa, ya) {
520520
var trace = calcTrace[0].trace;
521+
var texttemplate = trace.texttemplate;
521522

522523
var value;
523-
if(!trace.textinfo) {
524-
value = helpers.getValue(trace.text, index);
525-
} else {
524+
if(texttemplate) {
525+
value = calcTexttemplate(fullLayout, calcTrace, index, xa, ya);
526+
} else if(trace.textinfo) {
526527
value = calcTextinfo(calcTrace, index, xa, ya);
528+
} else {
529+
value = helpers.getValue(trace.text, index);
527530
}
528531

529532
return helpers.coerceString(attributeText, value);
@@ -534,6 +537,64 @@ function getTextPosition(trace, index) {
534537
return helpers.coerceEnumerated(attributeTextPosition, value);
535538
}
536539

540+
function calcTexttemplate(fullLayout, calcTrace, index, xa, ya) {
541+
var trace = calcTrace[0].trace;
542+
var texttemplate = Lib.castOption(trace, index, 'texttemplate');
543+
if(!texttemplate) return '';
544+
var isHorizontal = (trace.orientation === 'h');
545+
var isWaterfall = (trace.type === 'waterfall');
546+
var isFunnel = (trace.type === 'funnel');
547+
548+
function formatLabel(u) {
549+
var pAxis = isHorizontal ? ya : xa;
550+
return tickText(pAxis, u, true).text;
551+
}
552+
553+
function formatNumber(v) {
554+
var sAxis = isHorizontal ? xa : ya;
555+
return tickText(sAxis, +v, true).text;
556+
}
557+
558+
var cdi = calcTrace[index];
559+
var obj = {};
560+
561+
obj.label = cdi.p;
562+
obj.labelLabel = formatLabel(cdi.p);
563+
564+
var tx = Lib.castOption(trace, cdi.i, 'text');
565+
if(tx === 0 || tx) obj.text = tx;
566+
567+
obj.value = cdi.s;
568+
obj.valueLabel = formatNumber(cdi.s);
569+
570+
obj.x = cdi.x;
571+
obj.y = cdi.y;
572+
573+
if(isWaterfall) {
574+
obj.delta = +cdi.rawS || cdi.s;
575+
obj.deltaLabel = formatNumber(obj.delta);
576+
obj.final = cdi.v;
577+
obj.finalLabel = formatNumber(obj.final);
578+
obj.initial = obj.final - obj.delta;
579+
obj.initialLabel = formatNumber(obj.initial);
580+
}
581+
582+
if(isFunnel) {
583+
obj.value = cdi.s;
584+
obj.valueLabel = formatNumber(obj.value);
585+
586+
obj.percentInitial = cdi.begR;
587+
obj.percentInitialLabel = Lib.formatPercent(cdi.begR);
588+
obj.percentPrevious = cdi.difR;
589+
obj.percentPreviousLabel = Lib.formatPercent(cdi.difR);
590+
obj.percentTotal = cdi.sumR;
591+
obj.percenTotalLabel = Lib.formatPercent(cdi.sumR);
592+
}
593+
594+
return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, obj, trace._meta || {});
595+
}
596+
597+
// TODO: calcTextinfo should build a texttemplate pass it to calcTexttemplate()
537598
function calcTextinfo(calcTrace, index, xa, ya) {
538599
var trace = calcTrace[0].trace;
539600
var isHorizontal = (trace.orientation === 'h');

src/traces/funnel/attributes.js

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var barAttrs = require('../bar/attributes');
1212
var lineAttrs = require('../scatter/attributes').line;
1313
var plotAttrs = require('../../plots/attributes');
1414
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
15+
var texttemplateAttrs = require('../../components/fx/texttemplate_attributes');
1516
var constants = require('./constants');
1617
var extendFlat = require('../../lib/extend').extendFlat;
1718
var Color = require('../../components/color');
@@ -46,6 +47,9 @@ module.exports = {
4647
'are computed separately (per trace).'
4748
].join(' ')
4849
},
50+
texttemplate: texttemplateAttrs({editType: 'plot'}, {
51+
keys: constants.eventDataKeys
52+
}),
4953

5054
text: barAttrs.text,
5155
textposition: extendFlat({}, barAttrs.textposition, {dflt: 'auto'}),

src/traces/funnel/defaults.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
3232
coerce('width');
3333

3434
var text = coerce('text');
35+
coerce('texttemplate');
3536

3637
coerce('hovertext');
3738
coerce('hovertemplate');
@@ -46,7 +47,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
4647
moduleHasInsideanchor: true
4748
});
4849

49-
if(traceOut.textposition !== 'none') {
50+
if(traceOut.textposition !== 'none' && !traceOut.texttemplate) {
5051
coerce('textinfo', Array.isArray(text) ? 'text+value' : 'value');
5152
}
5253

src/traces/funnelarea/attributes.js

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var pieAttrs = require('../pie/attributes');
1212
var plotAttrs = require('../../plots/attributes');
1313
var domainAttrs = require('../../plots/domain').attributes;
1414
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
15+
var texttemplateAttrs = require('../../components/fx/texttemplate_attributes');
1516

1617
var extendFlat = require('../../lib/extend').extendFlat;
1718

@@ -53,6 +54,10 @@ module.exports = {
5354
flags: ['label', 'text', 'value', 'percent']
5455
}),
5556

57+
texttemplate: texttemplateAttrs({editType: 'plot'}, {
58+
keys: ['label', 'color', 'value', 'percent', 'text']
59+
}),
60+
5661
hoverinfo: extendFlat({}, plotAttrs.hoverinfo, {
5762
flags: ['label', 'text', 'value', 'percent', 'name']
5863
}),

src/traces/funnelarea/defaults.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
4646
coerce('scalegroup');
4747

4848
var textData = coerce('text');
49-
var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
49+
var textTemplate = coerce('texttemplate');
50+
var textInfo;
51+
if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
52+
5053
coerce('hovertext');
5154
coerce('hovertemplate');
5255

53-
if(textInfo && textInfo !== 'none') {
56+
if(textTemplate || (textInfo && textInfo !== 'none')) {
5457
var textposition = coerce('textposition');
5558
handleText(traceIn, traceOut, layout, coerce, textposition, {
5659
moduleHasSelected: false,

src/traces/funnelarea/plot.js

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ module.exports = function plot(gd, cdModule) {
7878
slicePath.attr('d', shape);
7979

8080
// add text
81+
piePlot.formatSliceLabel(gd, pt, cd0);
8182
var textPosition = pieHelpers.castOption(trace.textposition, pt.pts);
8283
var sliceTextGroup = sliceTop.selectAll('g.slicetext')
8384
.data(pt.text && (textPosition !== 'none') ? [0] : []);

src/traces/pie/attributes.js

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var domainAttrs = require('../../plots/domain').attributes;
1313
var fontAttrs = require('../../plots/font_attributes');
1414
var colorAttrs = require('../../components/color/attributes');
1515
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
16+
var texttemplateAttrs = require('../../components/fx/texttemplate_attributes');
1617

1718
var extendFlat = require('../../lib/extend').extendFlat;
1819

@@ -163,6 +164,9 @@ module.exports = {
163164
hovertemplate: hovertemplateAttrs({}, {
164165
keys: ['label', 'color', 'value', 'percent', 'text']
165166
}),
167+
texttemplate: texttemplateAttrs({editType: 'plot'}, {
168+
keys: ['label', 'color', 'value', 'percent', 'text']
169+
}),
166170
textposition: {
167171
valType: 'enumerated',
168172
role: 'info',

0 commit comments

Comments
 (0)