diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js
index 32744bcbaea..7343d3bb58b 100644
--- a/src/components/drawing/index.js
+++ b/src/components/drawing/index.js
@@ -28,6 +28,7 @@ var subTypes = require('../../traces/scatter/subtypes');
var makeBubbleSizeFn = require('../../traces/scatter/make_bubble_size_func');
var drawing = module.exports = {};
+var appendArrayPointValue = require('../fx/helpers').appendArrayPointValue;
// -----------------------------------------------------
// styling functions for plot elements
@@ -679,7 +680,7 @@ function extracTextFontSize(d, trace) {
}
// draw text at points
-drawing.textPointStyle = function(s, trace, gd) {
+drawing.textPointStyle = function(s, trace, gd, inLegend) {
if(!s.size()) return;
var selectedTextColorFn;
@@ -689,15 +690,24 @@ drawing.textPointStyle = function(s, trace, gd) {
selectedTextColorFn = fns.selectedTextColorFn;
}
+ var template = trace.texttemplate;
+ // If styling text in legends, do not use texttemplate
+ if(inLegend) template = false;
s.each(function(d) {
var p = d3.select(this);
- var text = Lib.extractOption(d, trace, 'tx', 'text');
+ var text = Lib.extractOption(d, trace, template ? 'txt' : 'tx', template ? 'texttemplate' : 'text');
if(!text && text !== 0) {
p.remove();
return;
}
+ if(template) {
+ var pt = {};
+ appendArrayPointValue(pt, trace, d.i);
+ text = Lib.texttemplateString(text, {}, gd._fullLayout._d3locale, pt, d, trace._meta || {});
+ }
+
var pos = d.tp || trace.textposition;
var fontSize = extracTextFontSize(d, trace);
var fontColor = selectedTextColorFn ?
diff --git a/src/components/legend/style.js b/src/components/legend/style.js
index b93a1232771..85c222e4671 100644
--- a/src/components/legend/style.js
+++ b/src/components/legend/style.js
@@ -271,7 +271,7 @@ module.exports = function style(s, gd) {
.append('g').classed('pointtext', true)
.append('text').attr('transform', 'translate(20,0)');
txt.exit().remove();
- txt.selectAll('text').call(Drawing.textPointStyle, tMod, gd);
+ txt.selectAll('text').call(Drawing.textPointStyle, tMod, gd, true);
}
function styleWaterfalls(d) {
diff --git a/src/lib/index.js b/src/lib/index.js
index 726cc5e0a30..0073533aab1 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -964,7 +964,7 @@ lib.numSeparate = function(value, separators, separatethousands) {
return x1 + x2;
};
-lib.TEMPLATE_STRING_REGEX = /%{([^\s%{}:]*)(:[^}]*)?}/g;
+lib.TEMPLATE_STRING_REGEX = /%{([^\s%{}:]*)([:|\|][^}]*)?}/g;
var SIMPLE_PROPERTY_REGEX = /^\w*$/;
/**
@@ -993,9 +993,25 @@ lib.templateString = function(string, obj) {
});
};
-var TEMPLATE_STRING_FORMAT_SEPARATOR = /^:/;
-var numberOfHoverTemplateWarnings = 0;
-var maximumNumberOfHoverTemplateWarnings = 10;
+var hovertemplateWarnings = {
+ max: 10,
+ count: 0,
+ name: 'hovertemplate'
+};
+lib.hovertemplateString = function() {
+ return templateFormatString.apply(hovertemplateWarnings, arguments);
+};
+
+var texttemplateWarnings = {
+ max: 10,
+ count: 0,
+ name: 'texttemplate'
+};
+lib.texttemplateString = function() {
+ return templateFormatString.apply(texttemplateWarnings, arguments);
+};
+
+var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/;
/**
* Substitute values from an object into a string and optionally formats them using d3-format,
* or fallback to associated labels.
@@ -1005,15 +1021,17 @@ var maximumNumberOfHoverTemplateWarnings = 10;
* Lib.hovertemplateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf'
* Lib.hovertemplateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00'
*
- * @param {obj} d3 locale
* @param {string} input string containing %{...:...} template strings
* @param {obj} data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'}
+ * @param {obj} d3 locale
* @param {obj} data objects containing substitution values
*
* @return {string} templated string
*/
-lib.hovertemplateString = function(string, labels, d3locale) {
+function templateFormatString(string, labels, d3locale) {
+ var opts = this;
var args = arguments;
+ if(!labels) labels = {};
// Not all that useful, but cache nestedProperty instantiation
// just in case it speeds things up *slightly*:
var getterCache = {};
@@ -1022,6 +1040,7 @@ lib.hovertemplateString = function(string, labels, d3locale) {
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;
@@ -1034,32 +1053,38 @@ lib.hovertemplateString = function(string, labels, d3locale) {
if(value !== undefined) break;
}
- if(value === undefined) {
- if(numberOfHoverTemplateWarnings < maximumNumberOfHoverTemplateWarnings) {
- lib.warn('Variable \'' + key + '\' in hovertemplate could not be found!');
+ if(value === undefined && opts) {
+ if(opts.count < opts.max) {
+ lib.warn('Variable \'' + key + '\' in ' + opts.name + ' could not be found!');
value = match;
}
- if(numberOfHoverTemplateWarnings === maximumNumberOfHoverTemplateWarnings) {
- lib.warn('Too many hovertemplate warnings - additional warnings will be suppressed');
+ if(opts.count === opts.max) {
+ lib.warn('Too many ' + opts.name + ' warnings - additional warnings will be suppressed');
}
- numberOfHoverTemplateWarnings++;
+ opts.count++;
+
+ return match;
}
if(format) {
var fmt;
- if(d3locale) {
- fmt = d3locale.numberFormat;
- } else {
- fmt = d3.format;
+ if(format[0] === ':') {
+ fmt = d3locale ? d3locale.numberFormat : d3.format;
+ value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value);
+ }
+
+ if(format[0] === '|') {
+ fmt = d3locale ? d3locale.timeFormat.utc : d3.time.format.utc;
+ var ms = lib.dateTime2ms(value);
+ value = lib.formatDate(ms, format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''), false, fmt);
}
- value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value);
} else {
if(labels.hasOwnProperty(key + 'Label')) value = labels[key + 'Label'];
}
return value;
});
-};
+}
/*
* alphanumeric string sort, tailored for subplot IDs like scene2, scene10, x10y13 etc
diff --git a/src/components/fx/hovertemplate_attributes.js b/src/plots/template_attributes.js
similarity index 53%
rename from src/components/fx/hovertemplate_attributes.js
rename to src/plots/template_attributes.js
index 23e53961500..fd5a5bdb59c 100644
--- a/src/components/fx/hovertemplate_attributes.js
+++ b/src/plots/template_attributes.js
@@ -8,12 +8,20 @@
'use strict';
-var FORMAT_LINK = require('../../constants/docs').FORMAT_LINK;
+var FORMAT_LINK = require('../constants/docs').FORMAT_LINK;
+var DATE_FORMAT_LINK = require('../constants/docs').DATE_FORMAT_LINK;
-module.exports = function(opts, extra) {
- opts = opts || {};
- extra = extra || {};
+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 describeVariables(extra) {
var descPart = extra.description ? ' ' + extra.description : '';
var keys = extra.keys || [];
if(keys.length > 0) {
@@ -28,6 +36,14 @@ module.exports = function(opts, extra) {
descPart = 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.';
}
}
+ return descPart;
+}
+
+exports.hovertemplateAttrs = function(opts, extra) {
+ opts = opts || {};
+ extra = extra || {};
+
+ var descPart = describeVariables(extra);
var hovertemplate = {
valType: 'string',
@@ -37,10 +53,7 @@ module.exports = function(opts, extra) {
description: [
'Template string used for rendering the information that appear on hover box.',
'Note that this will override `hoverinfo`.',
- '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.',
+ templateFormatStringDescription,
'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plot.ly/javascript/plotlyjs-events/#event-data.',
'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.',
descPart,
@@ -55,3 +68,29 @@ module.exports = function(opts, extra) {
return hovertemplate;
};
+
+exports.texttemplateAttrs = function(opts, extra) {
+ opts = opts || {};
+ extra = extra || {};
+
+ var descPart = describeVariables(extra);
+
+ var texttemplate = {
+ valType: 'string',
+ role: 'info',
+ dflt: '',
+ editType: opts.editType || 'calc',
+ description: [
+ 'Template string used for rendering the information text that appear on points.',
+ 'Note that this will override `textinfo`.',
+ templateFormatStringDescription,
+ 'Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.',
+ descPart
+ ].join(' ')
+ };
+
+ if(opts.arrayOk !== false) {
+ texttemplate.arrayOk = true;
+ }
+ return texttemplate;
+};
diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js
index 5c391df606f..c800d8bc30a 100644
--- a/src/traces/bar/attributes.js
+++ b/src/traces/bar/attributes.js
@@ -9,7 +9,8 @@
'use strict';
var scatterAttrs = require('../scatter/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var colorScaleAttrs = require('../../components/colorscale/attributes');
var fontAttrs = require('../../plots/font_attributes');
var constants = require('./constants.js');
@@ -59,6 +60,9 @@ module.exports = {
dy: scatterAttrs.dy,
text: scatterAttrs.text,
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: constants.eventDataKeys
+ }),
hovertext: scatterAttrs.hovertext,
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js
index e6df70f4562..5627ffa3db7 100644
--- a/src/traces/bar/defaults.js
+++ b/src/traces/bar/defaults.js
@@ -155,6 +155,8 @@ function handleText(traceIn, traceOut, layout, coerce, textposition, opts) {
if(moduleHasConstrain) coerce('constraintext');
if(moduleHasCliponaxis) coerce('cliponaxis');
if(moduleHasTextangle) coerce('textangle');
+
+ coerce('texttemplate');
}
if(hasInside) {
diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js
index d730165dac7..11d8ad24f68 100644
--- a/src/traces/bar/plot.js
+++ b/src/traces/bar/plot.js
@@ -26,6 +26,8 @@ var attributes = require('./attributes');
var attributeText = attributes.text;
var attributeTextPosition = attributes.textposition;
+var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;
+
// padding in pixels around text
var TEXTPAD = 3;
@@ -226,7 +228,7 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts) {
var trace = calcTrace[0].trace;
var isHorizontal = (trace.orientation === 'h');
- var text = getText(calcTrace, i, xa, ya);
+ var text = getText(fullLayout, calcTrace, i, xa, ya);
textPosition = getTextPosition(trace, i);
// compute text position
@@ -537,14 +539,17 @@ function getTransform(opts) {
return transformTranslate + transformScale + transformRotate;
}
-function getText(calcTrace, index, xa, ya) {
+function getText(fullLayout, calcTrace, index, xa, ya) {
var trace = calcTrace[0].trace;
+ var texttemplate = trace.texttemplate;
var value;
- if(!trace.textinfo) {
- value = helpers.getValue(trace.text, index);
- } else {
+ if(texttemplate) {
+ value = calcTexttemplate(fullLayout, calcTrace, index, xa, ya);
+ } else if(trace.textinfo) {
value = calcTextinfo(calcTrace, index, xa, ya);
+ } else {
+ value = helpers.getValue(trace.text, index);
}
return helpers.coerceString(attributeText, value);
@@ -555,6 +560,65 @@ function getTextPosition(trace, index) {
return helpers.coerceEnumerated(attributeTextPosition, value);
}
+function calcTexttemplate(fullLayout, calcTrace, index, xa, ya) {
+ var trace = calcTrace[0].trace;
+ var texttemplate = Lib.castOption(trace, index, 'texttemplate');
+ if(!texttemplate) return '';
+ var isHorizontal = (trace.orientation === 'h');
+ var isWaterfall = (trace.type === 'waterfall');
+ var isFunnel = (trace.type === 'funnel');
+
+ function formatLabel(u) {
+ var pAxis = isHorizontal ? ya : xa;
+ return tickText(pAxis, u, true).text;
+ }
+
+ function formatNumber(v) {
+ var sAxis = isHorizontal ? xa : ya;
+ return tickText(sAxis, +v, true).text;
+ }
+
+ var cdi = calcTrace[index];
+ var obj = {};
+
+ obj.label = cdi.p;
+ obj.labelLabel = formatLabel(cdi.p);
+
+ var tx = Lib.castOption(trace, cdi.i, 'text');
+ if(tx === 0 || tx) obj.text = tx;
+
+ obj.value = cdi.s;
+ obj.valueLabel = formatNumber(cdi.s);
+
+ var pt = {};
+ appendArrayPointValue(pt, trace, cdi.i);
+
+ if(isWaterfall) {
+ obj.delta = +cdi.rawS || cdi.s;
+ obj.deltaLabel = formatNumber(obj.delta);
+ obj.final = cdi.v;
+ obj.finalLabel = formatNumber(obj.final);
+ obj.initial = obj.final - obj.delta;
+ obj.initialLabel = formatNumber(obj.initial);
+ }
+
+ if(isFunnel) {
+ obj.value = cdi.s;
+ obj.valueLabel = formatNumber(obj.value);
+
+ obj.percentInitial = cdi.begR;
+ obj.percentInitialLabel = Lib.formatPercent(cdi.begR);
+ obj.percentPrevious = cdi.difR;
+ obj.percentPreviousLabel = Lib.formatPercent(cdi.difR);
+ obj.percentTotal = cdi.sumR;
+ obj.percenTotalLabel = Lib.formatPercent(cdi.sumR);
+ }
+
+ var customdata = Lib.castOption(trace, cdi.i, 'customdata');
+ if(customdata) obj.customdata = customdata;
+ return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, pt, obj, trace._meta || {});
+}
+
function calcTextinfo(calcTrace, index, xa, ya) {
var trace = calcTrace[0].trace;
var isHorizontal = (trace.orientation === 'h');
diff --git a/src/traces/barpolar/attributes.js b/src/traces/barpolar/attributes.js
index e6540f1a6c6..46de5c8f4bc 100644
--- a/src/traces/barpolar/attributes.js
+++ b/src/traces/barpolar/attributes.js
@@ -8,7 +8,7 @@
'use strict';
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var extendFlat = require('../../lib/extend').extendFlat;
var scatterPolarAttrs = require('../scatterpolar/attributes');
var barAttrs = require('../bar/attributes');
diff --git a/src/traces/box/attributes.js b/src/traces/box/attributes.js
index 66476cb1f26..a8144de00d0 100644
--- a/src/traces/box/attributes.js
+++ b/src/traces/box/attributes.js
@@ -11,7 +11,7 @@
var scatterAttrs = require('../scatter/attributes');
var barAttrs = require('../bar/attributes');
var colorAttrs = require('../../components/color/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var extendFlat = require('../../lib/extend').extendFlat;
var scatterMarkerAttrs = scatterAttrs.marker;
diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js
index 0e5145f18e5..16a3712413a 100644
--- a/src/traces/choropleth/attributes.js
+++ b/src/traces/choropleth/attributes.js
@@ -8,7 +8,7 @@
'use strict';
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var scatterGeoAttrs = require('../scattergeo/attributes');
var colorScaleAttrs = require('../../components/colorscale/attributes');
var plotAttrs = require('../../plots/attributes');
diff --git a/src/traces/choroplethmapbox/attributes.js b/src/traces/choroplethmapbox/attributes.js
index 4b36f3d44ee..31c932e1273 100644
--- a/src/traces/choroplethmapbox/attributes.js
+++ b/src/traces/choroplethmapbox/attributes.js
@@ -10,7 +10,7 @@
var choroplethAttrs = require('../choropleth/attributes');
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var extendFlat = require('../../lib/extend').extendFlat;
diff --git a/src/traces/cone/attributes.js b/src/traces/cone/attributes.js
index a47c8dc8ee5..5e263c86515 100644
--- a/src/traces/cone/attributes.js
+++ b/src/traces/cone/attributes.js
@@ -9,7 +9,7 @@
'use strict';
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var mesh3dAttrs = require('../mesh3d/attributes');
var baseAttrs = require('../../plots/attributes');
diff --git a/src/traces/densitymapbox/attributes.js b/src/traces/densitymapbox/attributes.js
index 4dcc168f8eb..c37046b1f76 100644
--- a/src/traces/densitymapbox/attributes.js
+++ b/src/traces/densitymapbox/attributes.js
@@ -9,7 +9,7 @@
'use strict';
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var plotAttrs = require('../../plots/attributes');
var scatterMapboxAttrs = require('../scattermapbox/attributes');
diff --git a/src/traces/funnel/attributes.js b/src/traces/funnel/attributes.js
index 5e5f5f2ed27..c21d05865a7 100644
--- a/src/traces/funnel/attributes.js
+++ b/src/traces/funnel/attributes.js
@@ -11,7 +11,8 @@
var barAttrs = require('../bar/attributes');
var lineAttrs = require('../scatter/attributes').line;
var plotAttrs = require('../../plots/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var constants = require('./constants');
var extendFlat = require('../../lib/extend').extendFlat;
var Color = require('../../components/color');
@@ -46,6 +47,10 @@ module.exports = {
'are computed separately (per trace).'
].join(' ')
},
+ // TODO: incorporate `label` and `value` in the eventData
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: constants.eventDataKeys.concat(['label', 'value'])
+ }),
text: barAttrs.text,
textposition: extendFlat({}, barAttrs.textposition, {dflt: 'auto'}),
diff --git a/src/traces/funnel/defaults.js b/src/traces/funnel/defaults.js
index f4ae0a1fd71..870f09cf9a8 100644
--- a/src/traces/funnel/defaults.js
+++ b/src/traces/funnel/defaults.js
@@ -46,7 +46,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
moduleHasInsideanchor: true
});
- if(traceOut.textposition !== 'none') {
+ if(traceOut.textposition !== 'none' && !traceOut.texttemplate) {
coerce('textinfo', Array.isArray(text) ? 'text+value' : 'value');
}
diff --git a/src/traces/funnelarea/attributes.js b/src/traces/funnelarea/attributes.js
index 7c995d007fb..d0bc5dbed7b 100644
--- a/src/traces/funnelarea/attributes.js
+++ b/src/traces/funnelarea/attributes.js
@@ -11,7 +11,8 @@
var pieAttrs = require('../pie/attributes');
var plotAttrs = require('../../plots/attributes');
var domainAttrs = require('../../plots/domain').attributes;
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var extendFlat = require('../../lib/extend').extendFlat;
@@ -53,12 +54,16 @@ module.exports = {
flags: ['label', 'text', 'value', 'percent']
}),
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['label', 'color', 'value', 'text', 'percent']
+ }),
+
hoverinfo: extendFlat({}, plotAttrs.hoverinfo, {
flags: ['label', 'text', 'value', 'percent', 'name']
}),
hovertemplate: hovertemplateAttrs({}, {
- keys: ['label', 'color', 'value', 'percent', 'text']
+ keys: ['label', 'color', 'value', 'text', 'percent']
}),
textposition: extendFlat({}, pieAttrs.textposition, {
diff --git a/src/traces/funnelarea/defaults.js b/src/traces/funnelarea/defaults.js
index ae20aba2d6f..a138b8d51ea 100644
--- a/src/traces/funnelarea/defaults.js
+++ b/src/traces/funnelarea/defaults.js
@@ -46,11 +46,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerce('scalegroup');
var textData = coerce('text');
- var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
+ var textTemplate = coerce('texttemplate');
+ var textInfo;
+ if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
+
coerce('hovertext');
coerce('hovertemplate');
- if(textInfo && textInfo !== 'none') {
+ if(textTemplate || (textInfo && textInfo !== 'none')) {
var textposition = coerce('textposition');
handleText(traceIn, traceOut, layout, coerce, textposition, {
moduleHasSelected: false,
diff --git a/src/traces/funnelarea/plot.js b/src/traces/funnelarea/plot.js
index 3103238e5d6..9e1091e2d66 100644
--- a/src/traces/funnelarea/plot.js
+++ b/src/traces/funnelarea/plot.js
@@ -79,6 +79,7 @@ module.exports = function plot(gd, cdModule) {
slicePath.attr('d', shape);
// add text
+ piePlot.formatSliceLabel(gd, pt, cd0);
var textPosition = pieHelpers.castOption(trace.textposition, pt.pts);
var sliceTextGroup = sliceTop.selectAll('g.slicetext')
.data(pt.text && (textPosition !== 'none') ? [0] : []);
diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js
index 1fc7aa07eb6..6be9ef1fc5a 100644
--- a/src/traces/heatmap/attributes.js
+++ b/src/traces/heatmap/attributes.js
@@ -9,7 +9,7 @@
'use strict';
var scatterAttrs = require('../scatter/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var colorScaleAttrs = require('../../components/colorscale/attributes');
var FORMAT_LINK = require('../../constants/docs').FORMAT_LINK;
diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js
index 4869e9b02fc..7f9a4d8a2a8 100644
--- a/src/traces/histogram/attributes.js
+++ b/src/traces/histogram/attributes.js
@@ -9,7 +9,7 @@
'use strict';
var barAttrs = require('../bar/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var makeBinAttrs = require('./bin_attributes');
var constants = require('./constants');
var extendFlat = require('../../lib/extend').extendFlat;
diff --git a/src/traces/histogram2d/attributes.js b/src/traces/histogram2d/attributes.js
index d4a6c6de48a..2fa6ffbd04f 100644
--- a/src/traces/histogram2d/attributes.js
+++ b/src/traces/histogram2d/attributes.js
@@ -11,7 +11,7 @@
var histogramAttrs = require('../histogram/attributes');
var makeBinAttrs = require('../histogram/bin_attributes');
var heatmapAttrs = require('../heatmap/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var colorScaleAttrs = require('../../components/colorscale/attributes');
var extendFlat = require('../../lib/extend').extendFlat;
diff --git a/src/traces/isosurface/attributes.js b/src/traces/isosurface/attributes.js
index 37cf47f316e..c294ca45f01 100644
--- a/src/traces/isosurface/attributes.js
+++ b/src/traces/isosurface/attributes.js
@@ -9,7 +9,7 @@
'use strict';
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var meshAttrs = require('../mesh3d/attributes');
var baseAttrs = require('../../plots/attributes');
diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js
index 4d538925f7d..db7c1963568 100644
--- a/src/traces/mesh3d/attributes.js
+++ b/src/traces/mesh3d/attributes.js
@@ -9,7 +9,7 @@
'use strict';
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var surfaceAttrs = require('../surface/attributes');
var baseAttrs = require('../../plots/attributes');
diff --git a/src/traces/parcats/attributes.js b/src/traces/parcats/attributes.js
index a2188a08636..8289c4a6642 100644
--- a/src/traces/parcats/attributes.js
+++ b/src/traces/parcats/attributes.js
@@ -12,7 +12,7 @@ var extendFlat = require('../../lib/extend').extendFlat;
var plotAttrs = require('../../plots/attributes');
var fontAttrs = require('../../plots/font_attributes');
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var domainAttrs = require('../../plots/domain').attributes;
var line = extendFlat(
diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js
index 475ae885fb0..04518282f6a 100644
--- a/src/traces/pie/attributes.js
+++ b/src/traces/pie/attributes.js
@@ -12,7 +12,8 @@ var plotAttrs = require('../../plots/attributes');
var domainAttrs = require('../../plots/domain').attributes;
var fontAttrs = require('../../plots/font_attributes');
var colorAttrs = require('../../components/color/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var extendFlat = require('../../lib/extend').extendFlat;
@@ -105,7 +106,7 @@ module.exports = {
text: {
valType: 'data_array',
- editType: 'calc',
+ editType: 'plot',
description: [
'Sets text elements associated with each sector.',
'If trace `textinfo` contains a *text* flag, these elements will be seen',
@@ -163,6 +164,9 @@ module.exports = {
hovertemplate: hovertemplateAttrs({}, {
keys: ['label', 'color', 'value', 'percent', 'text']
}),
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['label', 'color', 'value', 'percent', 'text']
+ }),
textposition: {
valType: 'enumerated',
role: 'info',
diff --git a/src/traces/pie/calc.js b/src/traces/pie/calc.js
index 461094aac19..89b361d29fc 100644
--- a/src/traces/pie/calc.js
+++ b/src/traces/pie/calc.js
@@ -13,8 +13,6 @@ var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray;
var tinycolor = require('tinycolor2');
var Color = require('../../components/color');
-var helpers = require('./helpers');
-var isValidTextValue = require('../../lib').isValidTextValue;
var extendedColorWayList = {};
@@ -93,32 +91,6 @@ function calc(gd, trace) {
// include the sum of all values in the first point
if(cd[0]) cd[0].vTotal = vTotal;
- // now insert text
- var textinfo = trace.textinfo;
- if(textinfo && textinfo !== 'none') {
- var parts = textinfo.split('+');
- var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
- var hasLabel = hasFlag('label');
- var hasText = hasFlag('text');
- var hasValue = hasFlag('value');
- var hasPercent = hasFlag('percent');
-
- var separators = fullLayout.separators;
- var text;
-
- for(i = 0; i < cd.length; i++) {
- pt = cd[i];
- text = hasLabel ? [pt.label] : [];
- if(hasText) {
- var tx = helpers.getFirstFilled(trace.text, pt.pts);
- if(isValidTextValue(tx)) text.push(tx);
- }
- if(hasValue) text.push(helpers.formatPieValue(pt.v, separators));
- if(hasPercent) text.push(helpers.formatPiePercent(pt.v / vTotal, separators));
- pt.text = text.join('
');
- }
- }
-
return cd;
}
diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js
index 3e94c24483d..c25b46b7671 100644
--- a/src/traces/pie/defaults.js
+++ b/src/traces/pie/defaults.js
@@ -47,11 +47,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
// TODO: hole needs to be coerced to the same value within a scaleegroup
var textData = coerce('text');
- var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
+ var textTemplate = coerce('texttemplate');
+ var textInfo;
+ if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
+
coerce('hovertext');
coerce('hovertemplate');
- if(textInfo && textInfo !== 'none') {
+ if(textTemplate || (textInfo && textInfo !== 'none')) {
var textposition = coerce('textposition');
handleText(traceIn, traceOut, layout, coerce, textposition, {
moduleHasSelected: false,
diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js
index bcc67a99d51..8f528c27aad 100644
--- a/src/traces/pie/plot.js
+++ b/src/traces/pie/plot.js
@@ -18,6 +18,7 @@ var svgTextUtils = require('../../lib/svg_text_utils');
var helpers = require('./helpers');
var eventData = require('./event_data');
+var isValidTextValue = require('../../lib').isValidTextValue;
function plot(gd, cdModule) {
var fullLayout = gd._fullLayout;
@@ -125,6 +126,7 @@ function plot(gd, cdModule) {
}
// add text
+ formatSliceLabel(gd, pt, cd0);
var textPosition = helpers.castOption(trace.textposition, pt.pts);
var sliceTextGroup = sliceTop.selectAll('g.slicetext')
.data(pt.text && (textPosition !== 'none') ? [0] : []);
@@ -941,8 +943,63 @@ function setCoords(cd) {
}
}
+function formatSliceLabel(gd, pt, cd0) {
+ var fullLayout = gd._fullLayout;
+ var trace = cd0.trace;
+ // look for textemplate
+ var texttemplate = trace.texttemplate;
+
+ // now insert text
+ var textinfo = trace.textinfo;
+ if(!texttemplate && textinfo && textinfo !== 'none') {
+ var parts = textinfo.split('+');
+ var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
+ var hasLabel = hasFlag('label');
+ var hasText = hasFlag('text');
+ var hasValue = hasFlag('value');
+ var hasPercent = hasFlag('percent');
+
+ var separators = fullLayout.separators;
+ var text;
+
+ text = hasLabel ? [pt.label] : [];
+ if(hasText) {
+ var tx = helpers.getFirstFilled(trace.text, pt.pts);
+ if(isValidTextValue(tx)) text.push(tx);
+ }
+ if(hasValue) text.push(helpers.formatPieValue(pt.v, separators));
+ if(hasPercent) text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
+ pt.text = text.join('
');
+ }
+
+ function makeTemplateVariables(pt) {
+ return {
+ label: pt.label,
+ value: pt.v,
+ valueLabel: helpers.formatPieValue(pt.v, fullLayout.separators),
+ percent: pt.v / cd0.vTotal,
+ percentLabel: helpers.formatPiePercent(pt.v / cd0.vTotal, fullLayout.separators),
+ color: pt.color,
+ text: pt.text,
+ customdata: Lib.castOption(trace, pt.i, 'customdata')
+ };
+ }
+
+ if(texttemplate) {
+ var txt = Lib.castOption(trace, pt.i, 'texttemplate');
+ if(!txt) {
+ pt.text = '';
+ } else {
+ var obj = makeTemplateVariables(pt);
+ var ptTx = helpers.getFirstFilled(trace.text, pt.pts);
+ if(isValidTextValue(ptTx)) obj.text = ptTx;
+ pt.text = Lib.texttemplateString(txt, obj, gd._fullLayout._d3locale, obj, trace._meta || {});
+ }
+ }
+}
module.exports = {
plot: plot,
+ formatSliceLabel: formatSliceLabel,
transformInsideText: transformInsideText,
determineInsideTextFont: determineInsideTextFont,
positionTitleOutside: positionTitleOutside,
diff --git a/src/traces/sankey/attributes.js b/src/traces/sankey/attributes.js
index 7fe25588f35..a3501323cf9 100644
--- a/src/traces/sankey/attributes.js
+++ b/src/traces/sankey/attributes.js
@@ -13,7 +13,7 @@ var plotAttrs = require('../../plots/attributes');
var colorAttrs = require('../../components/color/attributes');
var fxAttrs = require('../../components/fx/attributes');
var domainAttrs = require('../../plots/domain').attributes;
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var colorAttributes = require('../../components/colorscale/attributes');
var templatedArray = require('../../plot_api/plot_template').templatedArray;
diff --git a/src/traces/scatter/arrays_to_calcdata.js b/src/traces/scatter/arrays_to_calcdata.js
index 8842602402a..69086bc3e51 100644
--- a/src/traces/scatter/arrays_to_calcdata.js
+++ b/src/traces/scatter/arrays_to_calcdata.js
@@ -18,6 +18,7 @@ module.exports = function arraysToCalcdata(cd, trace) {
for(var i = 0; i < cd.length; i++) cd[i].i = i;
Lib.mergeArray(trace.text, cd, 'tx');
+ Lib.mergeArray(trace.texttemplate, cd, 'txt');
Lib.mergeArray(trace.hovertext, cd, 'htx');
Lib.mergeArray(trace.customdata, cd, 'data');
Lib.mergeArray(trace.textposition, cd, 'tp');
diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js
index e46a5f75513..209c4f9ec3e 100644
--- a/src/traces/scatter/attributes.js
+++ b/src/traces/scatter/attributes.js
@@ -8,7 +8,8 @@
'use strict';
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var colorScaleAttrs = require('../../components/colorscale/attributes');
var fontAttrs = require('../../plots/font_attributes');
var dash = require('../../components/drawing/attributes').dash;
@@ -166,6 +167,10 @@ module.exports = {
'these elements will be seen in the hover labels.'
].join(' ')
},
+
+ texttemplate: texttemplateAttrs({}, {
+
+ }),
hovertext: {
valType: 'string',
role: 'info',
diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js
index 3ff337d0e57..0efdc43345a 100644
--- a/src/traces/scatter/defaults.js
+++ b/src/traces/scatter/defaults.js
@@ -52,6 +52,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
}
if(subTypes.hasText(traceOut)) {
+ coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js
index d02559bf30c..e985957375e 100644
--- a/src/traces/scatter3d/attributes.js
+++ b/src/traces/scatter3d/attributes.js
@@ -10,7 +10,8 @@
var scatterAttrs = require('../scatter/attributes');
var colorAttributes = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var baseAttrs = require('../../plots/attributes');
var DASHES = require('../../constants/gl3d_dashes');
@@ -84,6 +85,9 @@ var attrs = module.exports = overrideAll({
'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,',
'these elements will be seen in the hover labels.'
].join(' ')
+ }),
+ texttemplate: texttemplateAttrs({}, {
+
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
description: [
diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js
index c903b3f8c93..79d48bed66f 100644
--- a/src/traces/scatter3d/convert.js
+++ b/src/traces/scatter3d/convert.js
@@ -22,6 +22,8 @@ var makeBubbleSizeFn = require('../scatter/make_bubble_size_func');
var DASH_PATTERNS = require('../../constants/gl3d_dashes');
var MARKER_SYMBOLS = require('../../constants/gl3d_markers');
+var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;
+
var calculateError = require('./calc_errors');
function LineWithMarkers(scene, uid) {
@@ -239,6 +241,22 @@ function convertPlotlyOptions(scene, data) {
for(i = 0; i < len; i++) text[i] = data.text;
}
+ // check texttemplate
+ var texttemplate = data.texttemplate;
+ if(texttemplate) {
+ var isArray = Array.isArray(texttemplate);
+ var N = isArray ? Math.min(texttemplate.length, len) : len;
+ var txt = isArray ? function(i) {return texttemplate[i];} : function() { return texttemplate;};
+ var d3locale = scene.fullLayout._d3locale;
+ text = new Array(N);
+ for(i = 0; i < N; i++) {
+ var pt = {};
+ pt.text = text[i];
+ appendArrayPointValue(pt, data, i);
+ text[i] = Lib.texttemplateString(txt(i), pt, d3locale, pt, data._meta || {});
+ }
+ }
+
// Build object parameters
params = {
position: points,
diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js
index 14de88e49ad..3ad572306a3 100644
--- a/src/traces/scatter3d/defaults.js
+++ b/src/traces/scatter3d/defaults.js
@@ -45,6 +45,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
}
if(subTypes.hasText(traceOut)) {
+ coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce, {noSelect: true});
}
diff --git a/src/traces/scatter3d/index.js b/src/traces/scatter3d/index.js
index 98fecd71957..58ea27a3e00 100644
--- a/src/traces/scatter3d/index.js
+++ b/src/traces/scatter3d/index.js
@@ -29,7 +29,7 @@ module.exports = {
moduleType: 'trace',
name: 'scatter3d',
basePlotModule: require('../../plots/gl3d'),
- categories: ['gl3d', 'symbols', 'showLegend'],
+ categories: ['gl3d', 'symbols', 'showLegend', 'scatter-like'],
meta: {
hrName: 'scatter_3d',
description: [
diff --git a/src/traces/scattercarpet/attributes.js b/src/traces/scattercarpet/attributes.js
index 3c2fed05336..88dd85fb404 100644
--- a/src/traces/scattercarpet/attributes.js
+++ b/src/traces/scattercarpet/attributes.js
@@ -10,7 +10,8 @@
var scatterAttrs = require('../scatter/attributes');
var plotAttrs = require('../../plots/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var colorScaleAttrs = require('../../components/colorscale/attributes');
var extendFlat = require('../../lib/extend').extendFlat;
@@ -52,6 +53,9 @@ module.exports = {
'these elements will be seen in the hover labels.'
].join(' ')
}),
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['a', 'b', 'text']
+ }),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
description: [
'Sets hover text elements associated with each (a,b) point.',
diff --git a/src/traces/scattercarpet/defaults.js b/src/traces/scattercarpet/defaults.js
index cea51be903d..e32d6aafec6 100644
--- a/src/traces/scattercarpet/defaults.js
+++ b/src/traces/scattercarpet/defaults.js
@@ -44,6 +44,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
traceOut._length = len;
coerce('text');
+ coerce('texttemplate');
coerce('hovertext');
var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines';
diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js
index 772d6f8c786..c327921cd58 100644
--- a/src/traces/scattergeo/attributes.js
+++ b/src/traces/scattergeo/attributes.js
@@ -8,7 +8,8 @@
'use strict';
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var scatterAttrs = require('../scatter/attributes');
var plotAttrs = require('../../plots/attributes');
var colorAttributes = require('../../components/colorscale/attributes');
@@ -64,6 +65,9 @@ module.exports = overrideAll({
'these elements will be seen in the hover labels.'
].join(' ')
}),
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['lat', 'lon', 'location', 'text']
+ }),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
description: [
'Sets hover text elements associated with each (lon,lat) pair',
diff --git a/src/traces/scattergeo/defaults.js b/src/traces/scattergeo/defaults.js
index d2b51fbfeb2..f4c8c8fad4f 100644
--- a/src/traces/scattergeo/defaults.js
+++ b/src/traces/scattergeo/defaults.js
@@ -46,6 +46,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
}
if(subTypes.hasText(traceOut)) {
+ coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js
index 4aa74ec4202..c56697489b2 100644
--- a/src/traces/scattergl/attributes.js
+++ b/src/traces/scattergl/attributes.js
@@ -98,3 +98,4 @@ var attrs = module.exports = overrideAll({
attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes';
attrs.hovertemplate = scatterAttrs.hovertemplate;
+attrs.texttemplate = scatterAttrs.texttemplate;
diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js
index 27916fca170..92536d4ddce 100644
--- a/src/traces/scattergl/convert.js
+++ b/src/traces/scattergl/convert.js
@@ -28,6 +28,8 @@ var TEXTOFFSETSIGN = {
start: 1, left: 1, end: -1, right: -1, middle: 0, center: 0, bottom: 1, top: -1
};
+var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;
+
function convertStyle(gd, trace) {
var i;
@@ -47,7 +49,7 @@ function convertStyle(gd, trace) {
if(trace.visible !== true) return opts;
if(subTypes.hasText(trace)) {
- opts.text = convertTextStyle(trace);
+ opts.text = convertTextStyle(trace, gd);
opts.textSel = convertTextSelection(trace, trace.selected);
opts.textUnsel = convertTextSelection(trace, trace.unselected);
}
@@ -100,7 +102,7 @@ function convertStyle(gd, trace) {
return opts;
}
-function convertTextStyle(trace) {
+function convertTextStyle(trace, gd) {
var count = trace._length;
var textfontIn = trace.textfont;
var textpositionIn = trace.textposition;
@@ -111,13 +113,33 @@ function convertTextStyle(trace) {
var optsOut = {};
var i;
- optsOut.text = trace.text;
- if(Array.isArray(optsOut.text) && optsOut.text.length < count) {
- optsOut.text = trace.text.slice();
+ var texttemplate = trace.texttemplate;
+ if(texttemplate) {
+ optsOut.text = [];
+ var isArray = Array.isArray(texttemplate);
+ var N = isArray ? Math.min(texttemplate.length, count) : count;
+ var txt = isArray ? function(i) {return texttemplate[i];} : function() { return texttemplate;};
+ var d3locale = gd._fullLayout._d3locale;
+ for(i = 0; i < N; i++) {
+ var pt = {};
+ appendArrayPointValue(pt, trace, i);
+ optsOut.text.push(Lib.texttemplateString(txt(i), pt, d3locale, pt, trace._meta || {}));
+ }
+ } else {
+ if(Array.isArray(trace.text) && trace.text.length < count) {
+ // if text array is shorter, we'll need to append to it, so let's slice to prevent mutating
+ optsOut.text = trace.text.slice();
+ } else {
+ optsOut.text = trace.text;
+ }
+ }
+ // pad text array with empty strings
+ if(Array.isArray(optsOut.text)) {
for(i = optsOut.text.length; i < count; i++) {
optsOut.text[i] = '';
}
}
+
optsOut.opacity = trace.opacity;
optsOut.font = {};
optsOut.align = [];
diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js
index 82a05c4e52f..5987ca12d7b 100644
--- a/src/traces/scattergl/defaults.js
+++ b/src/traces/scattergl/defaults.js
@@ -52,6 +52,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
}
if(subTypes.hasText(traceOut)) {
+ coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js
index 7fb71c44e11..c9f96a02630 100644
--- a/src/traces/scattermapbox/attributes.js
+++ b/src/traces/scattermapbox/attributes.js
@@ -8,7 +8,8 @@
'use strict';
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var scatterGeoAttrs = require('../scattergeo/attributes');
var scatterAttrs = require('../scatter/attributes');
var mapboxAttrs = require('../../plots/mapbox/layout_attributes');
@@ -49,6 +50,9 @@ module.exports = overrideAll({
'these elements will be seen in the hover labels.'
].join(' ')
}),
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['lat', 'lon', 'text']
+ }),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
description: [
'Sets hover text elements associated with each (lon,lat) pair',
diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js
index ace20bea379..9aa9c13b79c 100644
--- a/src/traces/scattermapbox/convert.js
+++ b/src/traces/scattermapbox/convert.js
@@ -20,7 +20,7 @@ var makeBubbleSizeFn = require('../scatter/make_bubble_size_func');
var subTypes = require('../scatter/subtypes');
var convertTextOpts = require('../../plots/mapbox/convert_text_opts');
-module.exports = function convert(calcTrace) {
+module.exports = function convert(gd, calcTrace) {
var trace = calcTrace[0].trace;
var isVisible = (trace.visible === true && trace._length !== 0);
@@ -87,7 +87,7 @@ module.exports = function convert(calcTrace) {
}
if(hasSymbols || hasText) {
- symbol.geojson = makeSymbolGeoJSON(calcTrace);
+ symbol.geojson = makeSymbolGeoJSON(calcTrace, gd);
Lib.extendFlat(symbol.layout, {
visibility: 'visible',
@@ -229,7 +229,7 @@ function makeCircleOpts(calcTrace) {
};
}
-function makeSymbolGeoJSON(calcTrace) {
+function makeSymbolGeoJSON(calcTrace, gd) {
var trace = calcTrace[0].trace;
var marker = trace.marker || {};
@@ -251,6 +251,16 @@ function makeSymbolGeoJSON(calcTrace) {
if(isBADNUM(calcPt.lonlat)) continue;
+ var txt = trace.texttemplate;
+ if(txt) {
+ var txti = Array.isArray(txt) ? (txt[i] || '') : txt;
+ calcPt.text = calcPt.tx;
+ calcPt.lon = calcPt.lonlat[0];
+ calcPt.lat = calcPt.lonlat[1];
+ calcPt.customdata = calcPt.data;
+ calcPt.txt = Lib.texttemplateString(txti, {}, gd._fullLayout._d3locale, calcPt, trace._meta || {});
+ }
+
features.push({
type: 'Feature',
geometry: {
@@ -259,7 +269,7 @@ function makeSymbolGeoJSON(calcTrace) {
},
properties: {
symbol: fillSymbol(calcPt.mx),
- text: fillText(calcPt.tx)
+ text: txt ? calcPt.txt : fillText(calcPt.tx)
}
});
}
diff --git a/src/traces/scattermapbox/defaults.js b/src/traces/scattermapbox/defaults.js
index 4708202f393..b0f088f9f14 100644
--- a/src/traces/scattermapbox/defaults.js
+++ b/src/traces/scattermapbox/defaults.js
@@ -29,6 +29,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
}
coerce('text');
+ coerce('texttemplate');
coerce('hovertext');
coerce('hovertemplate');
coerce('mode');
diff --git a/src/traces/scattermapbox/index.js b/src/traces/scattermapbox/index.js
index 279b1bad81e..ebe19400d3a 100644
--- a/src/traces/scattermapbox/index.js
+++ b/src/traces/scattermapbox/index.js
@@ -28,7 +28,7 @@ module.exports = {
moduleType: 'trace',
name: 'scattermapbox',
basePlotModule: require('../../plots/mapbox'),
- categories: ['mapbox', 'gl', 'symbols', 'showLegend', 'scatterlike'],
+ categories: ['mapbox', 'gl', 'symbols', 'showLegend', 'scatter-like'],
meta: {
hrName: 'scatter_mapbox',
description: [
diff --git a/src/traces/scattermapbox/plot.js b/src/traces/scattermapbox/plot.js
index e683980a722..55c2e910dcb 100644
--- a/src/traces/scattermapbox/plot.js
+++ b/src/traces/scattermapbox/plot.js
@@ -67,7 +67,7 @@ proto.addLayer = function(k, opts, below) {
proto.update = function update(calcTrace) {
var subplot = this.subplot;
var map = subplot.map;
- var optsAll = convert(calcTrace);
+ var optsAll = convert(subplot.gd, calcTrace);
var below = subplot.belowLookup['trace-' + this.uid];
var i, k, opts;
@@ -113,7 +113,7 @@ proto.dispose = function dispose() {
module.exports = function createScatterMapbox(subplot, calcTrace) {
var trace = calcTrace[0].trace;
var scatterMapbox = new ScatterMapbox(subplot, trace.uid);
- var optsAll = convert(calcTrace);
+ var optsAll = convert(subplot.gd, calcTrace);
var below = scatterMapbox.below = subplot.belowLookup['trace-' + trace.uid];
for(var i = 0; i < ORDER.length; i++) {
diff --git a/src/traces/scatterpolar/attributes.js b/src/traces/scatterpolar/attributes.js
index d1a2b86fce5..1e5b2742dc6 100644
--- a/src/traces/scatterpolar/attributes.js
+++ b/src/traces/scatterpolar/attributes.js
@@ -8,7 +8,8 @@
'use strict';
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var extendFlat = require('../../lib/extend').extendFlat;
var scatterAttrs = require('../scatter/attributes');
var plotAttrs = require('../../plots/attributes');
@@ -85,6 +86,9 @@ module.exports = {
},
text: scatterAttrs.text,
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['r', 'theta', 'text']
+ }),
hovertext: scatterAttrs.hovertext,
line: {
diff --git a/src/traces/scatterpolar/defaults.js b/src/traces/scatterpolar/defaults.js
index 81f7178549a..cc1c626b5a1 100644
--- a/src/traces/scatterpolar/defaults.js
+++ b/src/traces/scatterpolar/defaults.js
@@ -48,6 +48,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
}
if(subTypes.hasText(traceOut)) {
+ coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
diff --git a/src/traces/scatterpolargl/attributes.js b/src/traces/scatterpolargl/attributes.js
index 14ebb4568da..bef0834efa1 100644
--- a/src/traces/scatterpolargl/attributes.js
+++ b/src/traces/scatterpolargl/attributes.js
@@ -10,6 +10,7 @@
var scatterPolarAttrs = require('../scatterpolar/attributes');
var scatterGlAttrs = require('../scattergl/attributes');
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
module.exports = {
mode: scatterPolarAttrs.mode,
@@ -22,6 +23,9 @@ module.exports = {
thetaunit: scatterPolarAttrs.thetaunit,
text: scatterPolarAttrs.text,
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['r', 'theta', 'text']
+ }),
hovertext: scatterPolarAttrs.hovertext,
hovertemplate: scatterPolarAttrs.hovertemplate,
diff --git a/src/traces/scatterpolargl/defaults.js b/src/traces/scatterpolargl/defaults.js
index 3f534d7a207..5164d8f10e5 100644
--- a/src/traces/scatterpolargl/defaults.js
+++ b/src/traces/scatterpolargl/defaults.js
@@ -47,6 +47,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
}
if(subTypes.hasText(traceOut)) {
+ coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js
index 9b86099c84b..990b9dbc9cf 100644
--- a/src/traces/scatterternary/attributes.js
+++ b/src/traces/scatterternary/attributes.js
@@ -8,7 +8,8 @@
'use strict';
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var scatterAttrs = require('../scatter/attributes');
var plotAttrs = require('../../plots/attributes');
var colorScaleAttrs = require('../../components/colorscale/attributes');
@@ -80,6 +81,9 @@ module.exports = {
'these elements will be seen in the hover labels.'
].join(' ')
}),
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['a', 'b', 'c', 'text']
+ }),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
description: [
'Sets hover text elements associated with each (a,b,c) point.',
diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js
index 920df2a0317..23d3bbda305 100644
--- a/src/traces/scatterternary/defaults.js
+++ b/src/traces/scatterternary/defaults.js
@@ -75,6 +75,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
}
if(subTypes.hasText(traceOut)) {
+ coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
diff --git a/src/traces/splom/attributes.js b/src/traces/splom/attributes.js
index e75878fdf25..037079f95cb 100644
--- a/src/traces/splom/attributes.js
+++ b/src/traces/splom/attributes.js
@@ -10,7 +10,7 @@
var scatterAttrs = require('../scatter/attributes');
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var scatterGlAttrs = require('../scattergl/attributes');
var cartesianIdRegex = require('../../plots/cartesian/constants').idRegex;
var templatedArray = require('../../plot_api/plot_template').templatedArray;
diff --git a/src/traces/streamtube/attributes.js b/src/traces/streamtube/attributes.js
index 47b4f2f103b..9cf58c2b3fb 100644
--- a/src/traces/streamtube/attributes.js
+++ b/src/traces/streamtube/attributes.js
@@ -9,7 +9,7 @@
'use strict';
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var mesh3dAttrs = require('../mesh3d/attributes');
var baseAttrs = require('../../plots/attributes');
diff --git a/src/traces/sunburst/attributes.js b/src/traces/sunburst/attributes.js
index 88bddf3ef9e..f36294917aa 100644
--- a/src/traces/sunburst/attributes.js
+++ b/src/traces/sunburst/attributes.js
@@ -9,7 +9,8 @@
'use strict';
var plotAttrs = require('../../plots/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var domainAttrs = require('../../plots/domain').attributes;
var pieAttrs = require('../pie/attributes');
@@ -129,6 +130,9 @@ module.exports = {
editType: 'plot',
flags: ['label', 'text', 'value']
}),
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: ['label', 'text', 'value', 'color']
+ }),
textfont: pieAttrs.textfont,
hovertext: pieAttrs.hovertext,
diff --git a/src/traces/sunburst/defaults.js b/src/traces/sunburst/defaults.js
index 5a8a1d3bc2f..c34e27b019e 100644
--- a/src/traces/sunburst/defaults.js
+++ b/src/traces/sunburst/defaults.js
@@ -40,7 +40,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerce('leaf.opacity');
var text = coerce('text');
- coerce('textinfo', Array.isArray(text) ? 'text+label' : 'label');
+ coerce('texttemplate');
+ if(!traceOut.texttemplate) coerce('textinfo', Array.isArray(text) ? 'text+label' : 'label');
coerce('hovertext');
coerce('hovertemplate');
diff --git a/src/traces/sunburst/plot.js b/src/traces/sunburst/plot.js
index 0a0eed4b4ba..c7bd78f7e92 100644
--- a/src/traces/sunburst/plot.js
+++ b/src/traces/sunburst/plot.js
@@ -676,30 +676,51 @@ function attachFxHandlers(sliceTop, gd, cd) {
}
function formatSliceLabel(pt, trace, fullLayout) {
+ var texttemplate = trace.texttemplate;
var textinfo = trace.textinfo;
- if(!textinfo || textinfo === 'none') {
+ if(!texttemplate && (!textinfo || textinfo === 'none')) {
return '';
}
var cdi = pt.data.data;
var separators = fullLayout.separators;
- var parts = textinfo.split('+');
- var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
- var thisText = [];
+ if(!texttemplate) {
+ var parts = textinfo.split('+');
+ var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
+ var thisText = [];
- if(hasFlag('label') && cdi.label) thisText.push(cdi.label);
+ if(hasFlag('label') && cdi.label) {
+ thisText.push(cdi.label);
+ }
- if(cdi.hasOwnProperty('v') && hasFlag('value')) {
- thisText.push(formatPieValue(cdi.v, separators));
- }
+ if(cdi.hasOwnProperty('v') && hasFlag('value')) {
+ thisText.push(formatPieValue(cdi.v, separators));
+ }
- if(hasFlag('text')) {
- var tx = Lib.castOption(trace, cdi.i, 'text');
- if(Lib.isValidTextValue(tx)) thisText.push(tx);
+ if(hasFlag('text')) {
+ var tx = Lib.castOption(trace, cdi.i, 'text');
+ if(Lib.isValidTextValue(tx)) thisText.push(tx);
+ }
+
+ return thisText.join('
');
}
- return thisText.join('
');
+ var txt = Lib.castOption(trace, cdi.i, 'texttemplate');
+ if(!txt) return '';
+ var obj = {};
+ if(cdi.label) obj.label = cdi.label;
+ if(cdi.hasOwnProperty('v')) {
+ obj.value = cdi.v;
+ obj.valueLabel = formatPieValue(cdi.v, separators);
+ }
+ if(cdi.hasOwnProperty('color')) {
+ obj.color = cdi.color;
+ }
+ var ptTx = Lib.castOption(trace, cdi.i, 'text');
+ if(Lib.isValidTextValue(ptTx)) obj.text = ptTx;
+ obj.customdata = Lib.castOption(trace, cdi.i, 'customdata');
+ return Lib.texttemplateString(txt, obj, fullLayout._d3locale, obj, trace._meta || {});
}
function getInscribedRadiusFraction(pt) {
diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js
index 7e0fbfe0a6f..6d9ae82c2f1 100644
--- a/src/traces/surface/attributes.js
+++ b/src/traces/surface/attributes.js
@@ -10,7 +10,7 @@
var Color = require('../../components/color');
var colorScaleAttrs = require('../../components/colorscale/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var baseAttrs = require('../../plots/attributes');
var extendFlat = require('../../lib/extend').extendFlat;
diff --git a/src/traces/waterfall/attributes.js b/src/traces/waterfall/attributes.js
index 08ddfb6bdd2..94f0745cd02 100644
--- a/src/traces/waterfall/attributes.js
+++ b/src/traces/waterfall/attributes.js
@@ -11,7 +11,8 @@
var barAttrs = require('../bar/attributes');
var lineAttrs = require('../scatter/attributes').line;
var plotAttrs = require('../../plots/attributes');
-var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
+var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
+var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;
var constants = require('./constants');
var extendFlat = require('../../lib/extend').extendFlat;
var Color = require('../../components/color');
@@ -98,7 +99,10 @@ module.exports = {
'are computed separately (per trace).'
].join(' ')
},
-
+ // TODO: incorporate `label` and `value` in the eventData
+ texttemplate: texttemplateAttrs({editType: 'plot'}, {
+ keys: constants.eventDataKeys.concat(['label'])
+ }),
text: barAttrs.text,
textposition: barAttrs.textposition,
insidetextanchor: barAttrs.insidetextanchor,
diff --git a/src/traces/waterfall/defaults.js b/src/traces/waterfall/defaults.js
index cbff5eb6427..7e79d568591 100644
--- a/src/traces/waterfall/defaults.js
+++ b/src/traces/waterfall/defaults.js
@@ -62,7 +62,8 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
if(traceOut.textposition !== 'none') {
- coerce('textinfo');
+ coerce('texttemplate');
+ if(!traceOut.texttemplate) coerce('textinfo');
}
handleDirection(coerce, 'increasing', INCREASING_COLOR);
diff --git a/test/image/baselines/gl2d_texttemplate.png b/test/image/baselines/gl2d_texttemplate.png
new file mode 100644
index 00000000000..778520f3608
Binary files /dev/null and b/test/image/baselines/gl2d_texttemplate.png differ
diff --git a/test/image/baselines/gl3d_scatter3d-texttemplate.png b/test/image/baselines/gl3d_scatter3d-texttemplate.png
new file mode 100644
index 00000000000..46897a1e65d
Binary files /dev/null and b/test/image/baselines/gl3d_scatter3d-texttemplate.png differ
diff --git a/test/image/baselines/mapbox_texttemplate.png b/test/image/baselines/mapbox_texttemplate.png
new file mode 100644
index 00000000000..96878d67d19
Binary files /dev/null and b/test/image/baselines/mapbox_texttemplate.png differ
diff --git a/test/image/baselines/texttemplate.png b/test/image/baselines/texttemplate.png
new file mode 100644
index 00000000000..a1302aca93c
Binary files /dev/null and b/test/image/baselines/texttemplate.png differ
diff --git a/test/image/baselines/texttemplate_scatter.png b/test/image/baselines/texttemplate_scatter.png
new file mode 100644
index 00000000000..3d858675867
Binary files /dev/null and b/test/image/baselines/texttemplate_scatter.png differ
diff --git a/test/image/baselines/waterfall_funnel_texttemplate_date.png b/test/image/baselines/waterfall_funnel_texttemplate_date.png
new file mode 100644
index 00000000000..5a890f72b42
Binary files /dev/null and b/test/image/baselines/waterfall_funnel_texttemplate_date.png differ
diff --git a/test/image/mocks/gl2d_texttemplate.json b/test/image/mocks/gl2d_texttemplate.json
new file mode 100644
index 00000000000..e90aace0a8a
--- /dev/null
+++ b/test/image/mocks/gl2d_texttemplate.json
@@ -0,0 +1,61 @@
+{
+ "data": [{
+ "type": "scattergl",
+ "mode": "markers+text",
+ "x": [0, 1, 2, 3],
+ "y": [0, 1, 4, 9],
+
+ "text": ["a", "b", "c", "d"],
+ "texttemplate": "%{text}: (%{x}, %{y})",
+ "textposition": "top center",
+ "xaxis": "x2",
+ "yaxis": "y2"
+ }, {
+ "type": "scattergl",
+ "mode": "markers+text",
+ "x": [3, 2, 1, 0],
+ "y": [0, 1, 4, 9],
+ "texttemplate": ["1", "2", "3"],
+ "textposition": "top center",
+ "xaxis": "x2",
+ "yaxis": "y2"
+ },{
+ "type": "scatterpolargl",
+ "mode": "markers+text",
+ "text": ["A", "B", "C", "D"],
+ "texttemplate": "%{text}: (%{r:0.2f},%{theta:0.2f})",
+ "textposition": "top center",
+ "r": [1, 0.5, 1, 1.5],
+ "theta": [0, 90, 180, 270],
+ "showgrid": false,
+ "subplot": "polar"
+ }
+ ],
+ "layout": {
+ "showlegend": false,
+ "width": 600,
+ "height": 300,
+ "margin": {
+ "t": 50,
+ "b": 50,
+ "l": 50,
+ "r": 50
+ },
+ "polar": {
+ "radialaxis": {
+ "showline": false,
+ "linewidth": 0,
+ "tickwidth": 2,
+ "gridcolor": "white",
+ "gridwidth": 0
+ }
+ },
+ "grid": {
+ "rows": 1,
+ "columns": 2,
+ "pattern": "independent",
+ "xgap": 5,
+ "ygap": 5
+ }
+ }
+}
diff --git a/test/image/mocks/gl3d_scatter3d-texttemplate.json b/test/image/mocks/gl3d_scatter3d-texttemplate.json
new file mode 100644
index 00000000000..e74e981d3d0
--- /dev/null
+++ b/test/image/mocks/gl3d_scatter3d-texttemplate.json
@@ -0,0 +1,25 @@
+{
+ "data": [{
+ "type": "scatter3d",
+ "x": [0, 1, 2],
+ "y": [0, 2, 1],
+ "z": [-5, -2, 4],
+ "text": ["A", "B", "C"],
+ "texttemplate": "%{text}: (%{x}, %{y}, %{z})",
+ "mode": "markers+text"
+ }, {
+ "type": "scatter3d",
+ "x": [2, 1, 0],
+ "y": [0, 4, 1],
+ "z": [-5, -2, 4],
+ "texttemplate": ["1", "2 with %{meta.text}"],
+ "meta": {"text": "meta"},
+ "mode": "markers+text"
+ }],
+ "layout": {
+ "showlegend": true,
+ "width": 400,
+ "height": 400,
+ "margin": {"t": 20, "l": 0, "r": 0, "b": 0}
+ }
+}
diff --git a/test/image/mocks/mapbox_texttemplate.json b/test/image/mocks/mapbox_texttemplate.json
new file mode 100644
index 00000000000..68aa29cdcf9
--- /dev/null
+++ b/test/image/mocks/mapbox_texttemplate.json
@@ -0,0 +1,38 @@
+{
+ "data": [{
+ "type": "scattermapbox",
+ "mode": "markers+text",
+ "lon": [
+ -73.57,
+ -79.24,
+ -123.06
+ ],
+ "lat": [
+ 45.5,
+ 43.4,
+ 49.13
+ ],
+ "text": [
+ "Montreal",
+ "Toronto",
+ "Vancouver"
+ ],
+ "texttemplate": "%{text} (%{lon}, %{lat}): %{customdata:.2s}",
+ "textposition": "top center",
+ "customdata": [1780000, 2930000, 675218]
+ }],
+
+ "layout": {
+ "width": 500,
+ "height": 500,
+ "title": {"text": "Mapbox with texttemplate"},
+ "margin": {"t": 30, "b": 0, "l": 0, "r": 0},
+ "mapbox": {
+ "center": {
+ "lon": -90,
+ "lat": 45
+ },
+ "zoom": 1.5
+ }
+ }
+}
diff --git a/test/image/mocks/texttemplate.json b/test/image/mocks/texttemplate.json
new file mode 100644
index 00000000000..b7ce21629d0
--- /dev/null
+++ b/test/image/mocks/texttemplate.json
@@ -0,0 +1,196 @@
+{
+ "data": [
+ {
+ "type": "pie",
+ "values": [
+ 1,
+ 5,
+ 3,
+ 2
+ ],
+ "labels": [
+ "A",
+ "B",
+ "C",
+ "D"
+ ],
+ "text": [
+ "textA",
+ "textB",
+ "textC",
+ "textD"
+ ],
+ "texttemplate": "%{label}: %{value} (%{percent})",
+ "textposition": "inside",
+ "hovertemplate": "%{text}",
+ "domain": {
+ "row": 0,
+ "column": 0
+ }
+ },
+ {
+ "type": "funnelarea",
+ "values": [
+ 1,
+ 5,
+ 3,
+ 2
+ ],
+ "labels": [
+ "A",
+ "B",
+ "C",
+ "D"
+ ],
+ "text": [
+ "textA",
+ "textB",
+ "textC",
+ "textD"
+ ],
+ "texttemplate": "%{label}-%{color}-%{value}-%{percent}-%{text}",
+ "textposition": "inside",
+ "hovertemplate": "%{text}",
+ "domain": {
+ "row": 0,
+ "column": 1
+ }
+ },
+ {
+ "type": "sunburst",
+ "labels": [
+ "Eve",
+ "Cain",
+ "Seth",
+ "Enos",
+ "Esther"
+ ],
+ "values": [
+ 11,
+ 12,
+ 13,
+ 14,
+ 15
+ ],
+ "text": [
+ "txt1",
+ "txt2",
+ "txt3",
+ "txt4",
+ "txt5"
+ ],
+ "parents": [
+ "",
+ "Eve",
+ "Eve",
+ "Seth",
+ "Seth"
+ ],
+ "texttemplate": "%{label} %{value} %{text}",
+ "domain": {
+ "row": 0,
+ "column": 2
+ }
+ },
+ {
+ "type": "scatter",
+ "mode": "markers+lines+text",
+ "y": [
+ 1,
+ 5,
+ 3,
+ 2
+ ],
+ "texttemplate": "%{x}, %{y}",
+ "textposition": "top center",
+ "xaxis": "x5",
+ "yaxis": "y5"
+ },
+ {
+ "type": "bar",
+ "y": [
+ 1,
+ 5,
+ 3,
+ 2
+ ],
+ "text": [
+ "A",
+ "B",
+ "C",
+ "D"
+ ],
+ "texttemplate": "%{text}
%{value}",
+ "textposition": "inside",
+ "hovertemplate": "%{text}",
+ "xaxis": "x6",
+ "yaxis": "y6"
+ },
+ {
+ "type": "waterfall",
+ "y": [
+ 1,
+ 5,
+ 3,
+ 2
+ ],
+ "text": [
+ "A",
+ "B",
+ "C",
+ "D"
+ ],
+ "texttemplate": "%{text}-%{initial}-%{final}-%{delta}",
+ "textposition": "inside",
+ "hovertemplate": "%{text}",
+ "xaxis": "x7",
+ "yaxis": "y7"
+ },
+ {
+ "type": "funnel",
+ "orientation": "h",
+ "y": [
+ "A",
+ "B",
+ "C"
+ ],
+ "x": [
+ 3,
+ 2,
+ 1
+ ],
+ "textinfo": "value+percent initial+percent previous+percent total",
+ "texttemplate": "%{value}-%{percentInitial}-%{percentPrevious}-%{percentTotal:0.3f}",
+ "xaxis": "x8",
+ "yaxis": "y8"
+ }
+ ],
+ "layout": {
+ "width": 1000,
+ "height": 500,
+ "margin": {"t": 5, "b": 25, "l": 15, "r": 0},
+ "grid": {
+ "rows": 2,
+ "columns": 4,
+ "pattern": "independent",
+ "xgap": 5,
+ "ygap": 5
+ },
+ "xaxis": {
+ "type": "category",
+ "range": [
+ -0.5,
+ 3.5
+ ],
+ "autorange": true
+ },
+ "yaxis": {
+ "type": "linear",
+ "range": [
+ -2.1944444444444446,
+ 11.694444444444445
+ ],
+ "autorange": true
+ }
+ }
+}
diff --git a/test/image/mocks/texttemplate_scatter.json b/test/image/mocks/texttemplate_scatter.json
new file mode 100644
index 00000000000..3fff2e746f4
--- /dev/null
+++ b/test/image/mocks/texttemplate_scatter.json
@@ -0,0 +1,140 @@
+{
+ "data": [{
+ "type": "scattergeo",
+ "mode": "markers+text",
+ "lon": [
+ -73.57,
+ -79.24,
+ -123.06
+ ],
+ "lat": [
+ 45.5,
+ 43.4,
+ 49.13
+ ],
+ "text": [
+ "Montreal",
+ "Toronto",
+ "Vancouver"
+ ],
+ "texttemplate": "%{text}: (%{lon:.0f}, %{lat:0.f}): %{customdata:.2s}",
+ "textposition": "top center",
+ "customdata": [1780000, 2930000, 675218],
+ "geo": "geo"
+ }, {
+ "type": "carpet",
+ "carpet": "carpet1",
+ "a": [0.1, 0.2, 0.3],
+ "b": [1, 2, 3],
+ "y": [
+ [1, 2.2, 3],
+ [1.5, 2.7, 3.5],
+ [1.7, 2.9, 3.7]
+ ],
+ "cheaterslope": 1,
+
+ "xaxis": "x2",
+ "yaxis": "y2"
+ },
+ {
+ "type": "scattercarpet",
+ "carpet": "carpet1",
+ "name": "b = 1.5",
+ "mode": "markers+text",
+ "a": [0.1, 0.15, 0.25, 0.3],
+ "b": [1.5, 1.5, 1.5, 1.5],
+
+ "text": ["a", "b", "c", "d"],
+ "texttemplate": "%{text}: (%{a}, %{b})",
+ "textposition": "top center",
+ "xaxis": "x2",
+ "yaxis": "y2"
+ }, {
+ "type": "scatterpolar",
+ "mode": "markers+text",
+ "text": ["A", "B", "C", "D"],
+ "texttemplate": "%{text}: (%{r:0.2f},%{theta:0.2f})",
+ "textposition": "top center",
+ "r": [1, 0.5, 1, 1.5],
+ "theta": [0, 90, 180, 270],
+ "showgrid": false
+ }, {
+ "type": "scatterternary",
+ "a": [
+ 3,
+ 2,
+ 5
+ ],
+ "b": [
+ 2,
+ 5,
+ 2
+ ],
+ "c": [
+ 5,
+ 2,
+ 2
+ ],
+ "mode": "markers+text",
+ "text": ["A", "B", "C"],
+ "texttemplate": "%{text}
(%{a:.2f}, %{b:.2f}, %{c:.2f})",
+ "textposition": "bottom center"
+ }
+ ],
+ "layout": {
+ "showlegend": false,
+ "width": 800,
+ "height": 800,
+ "margin": {
+ "t": 50,
+ "b": 50,
+ "l": 50,
+ "r": 50
+ },
+ "geo": {
+ "scope": "north america",
+ "domain": {
+ "row": 1,
+ "column": 0
+ },
+ "lonaxis": {
+ "range": [
+ -130,
+ -55
+ ]
+ },
+ "lataxis": {
+ "range": [
+ 40,
+ 70
+ ]
+ },
+ "center": {
+ "lat": 57
+ }
+ },
+ "polar": {
+ "radialaxis": {
+ "showline": false,
+ "linewidth": 0,
+ "tickwidth": 2,
+ "gridcolor": "white",
+ "gridwidth": 0
+ }
+ },
+ "ternary": {
+ "sum": 10,
+ "domain": {
+ "row": 1,
+ "column": 1
+ }
+ },
+ "grid": {
+ "rows": 2,
+ "columns": 2,
+ "pattern": "independent",
+ "xgap": 5,
+ "ygap": 5
+ }
+ }
+}
diff --git a/test/image/mocks/waterfall_funnel_texttemplate_date.json b/test/image/mocks/waterfall_funnel_texttemplate_date.json
new file mode 100644
index 00000000000..b8d4be3e574
--- /dev/null
+++ b/test/image/mocks/waterfall_funnel_texttemplate_date.json
@@ -0,0 +1,73 @@
+{
+ "data": [{
+ "type": "waterfall",
+ "x": [
+ "2010-01-01",
+ "2010-07-01",
+ "2011-01-01",
+ "2012-01-01"
+ ],
+ "y": [1, -2, 3, 0],
+ "textinfo": "label",
+ "textposition": "inside"
+ }, {
+ "type": "waterfall",
+ "x": [
+ "2010-01-01",
+ "2010-07-01",
+ "2011-01-01",
+ "2012-01-01"
+ ],
+ "y": [1, -2, 3, 0],
+ "texttemplate": "%{label}",
+ "textposition": "inside",
+ "xaxis": "x1",
+ "yaxis": "y1"
+ }, {
+ "type": "funnel",
+ "orientation": "v",
+ "x": [
+ "2010-01-01",
+ "2010-07-01",
+ "2011-01-01",
+ "2012-01-01"
+ ],
+ "y": [10, 7, 3, 0],
+ "textinfo": "label",
+ "textposition": "inside",
+ "xaxis": "x2",
+ "yaxis": "y2"
+ }, {
+ "type": "funnel",
+ "orientation": "v",
+ "x": [
+ "2010-01-01",
+ "2010-07-01",
+ "2011-01-01",
+ "2012-01-01"
+ ],
+ "y": [10, 7, 3, 0],
+ "texttemplate": "%{label}",
+ "textposition": "inside",
+ "xaxis": "x2",
+ "yaxis": "y2"
+ }],
+ "layout": {
+ "title": {
+ "text": "textinfo: 'label' versus texttemplate: '%{label}'"
+ },
+ "xaxis": {
+ "type": "date"
+ },
+ "margin": {"t": 40, "b": 20, "l": 20, "r": 20},
+ "width": 800,
+ "height": 400,
+ "grid": {
+ "rows": 1,
+ "columns": 2,
+ "pattern": "independent",
+ "xgap": 5,
+ "ygap": 5
+ }
+ }
+}
diff --git a/test/jasmine/assets/check_texttemplate.js b/test/jasmine/assets/check_texttemplate.js
new file mode 100644
index 00000000000..4960865dbd7
--- /dev/null
+++ b/test/jasmine/assets/check_texttemplate.js
@@ -0,0 +1,123 @@
+var Plotly = require('@lib/index');
+var Registry = require('@src/registry');
+
+var Lib = require('@src/lib');
+var failTest = require('../assets/fail_test');
+var createGraphDiv = require('../assets/create_graph_div');
+var destroyGraphDiv = require('../assets/destroy_graph_div');
+var supplyAllDefaults = require('../assets/supply_defaults');
+
+'use strict';
+
+module.exports = function checkTextTemplate(mock, selector, tests) {
+ var isGL = Registry.traceIs(mock[0].type, 'gl');
+ var isPolar = Registry.traceIs(mock[0].type, 'polar');
+ var isScatterLike = Registry.traceIs(mock[0].type, 'scatter-like');
+ var isBarLike = Registry.traceIs(mock[0].type, 'bar-like');
+
+ it('should not coerce textinfo when texttemplate is defined', function() {
+ var gd = {};
+ gd.data = Lib.extendDeep([], mock);
+ gd.data[0].textinfo = 'text';
+ gd.data[0].texttemplate = 'texttemplate';
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].textinfo).toBe(undefined);
+ });
+
+ if(isScatterLike) {
+ it('should not coerce texttemplate when mode has no `text` flag', function() {
+ var gd = {};
+ gd.data = Lib.extendDeep([], mock);
+ gd.data[0].mode = 'markers';
+ gd.data[0].texttemplate = 'texttemplate';
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].texttemplate).toBe(undefined);
+ });
+ }
+
+ if(isBarLike) {
+ it('should not coerce texttemplate when textposition is `none`', function() {
+ var gd = {};
+ gd.data = Lib.extendDeep([], mock);
+ gd.data[0].textposition = 'none';
+ gd.data[0].texttemplate = 'texttemplate';
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].texttemplate).toBe(undefined);
+ });
+ }
+
+ var N = tests[0][1].length;
+ var i;
+
+ // Generate customdata
+ var customdata = [];
+ for(i = 0; i < N; i++) {
+ customdata.push(Lib.randstr({}));
+ }
+ mock[0].customdata = customdata;
+ tests.push(['%{customdata}', customdata]);
+
+ // Generate meta
+ mock[0].meta = {'colname': 'A'};
+ var metaSolution = [];
+ for(i = 0; i < N; i++) {
+ metaSolution.push(mock[0].meta.colname);
+ }
+ tests.push(['%{meta.colname}', metaSolution]);
+
+ if(isGL) {
+ tests.forEach(function(test) {
+ it('@gl should support texttemplate', function(done) {
+ var gd = createGraphDiv();
+ var mockCopy = Lib.extendDeep([], mock);
+ mockCopy[0].texttemplate = test[0];
+ Plotly.newPlot(gd, mockCopy)
+ .then(function() {
+ var glText;
+ if(isPolar) {
+ glText = gd._fullLayout.polar._subplot._scene.glText;
+ } else {
+ glText = gd._fullLayout._plots.xy._scene.glText;
+ }
+ expect(glText.length).toEqual(1);
+ expect(glText[0].textOffsets.length).toEqual(test[1].length);
+ for(var i = 0; i < glText[0].textOffsets.length - 1; i++) {
+ var from = glText[0].textOffsets[i];
+ var to = glText[0].textOffsets[i + 1];
+
+ var text = glText[0].text.slice(from, to);
+ expect(text).toEqual(test[1][i]);
+ }
+ })
+ .catch(failTest)
+ .finally(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ })
+ .then(done);
+ });
+ });
+ } else {
+ tests.forEach(function(test) {
+ it('should support texttemplate', function(done) {
+ var gd = createGraphDiv();
+ var mockCopy = Lib.extendDeep([], mock);
+ mockCopy[0].texttemplate = test[0];
+ Plotly.newPlot(gd, mockCopy)
+ .then(function() {
+ var pts = Plotly.d3.selectAll(selector);
+ expect(pts.size()).toBe(test[1].length);
+ pts.each(function() {
+ expect(test[1]).toContain(Plotly.d3.select(this).text());
+ });
+ })
+ .catch(failTest)
+ .finally(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ })
+ .then(done);
+ });
+ });
+ }
+};
diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js
index c4f191194aa..32d45cffb7f 100644
--- a/test/jasmine/tests/bar_test.js
+++ b/test/jasmine/tests/bar_test.js
@@ -25,6 +25,7 @@ var customAssertions = require('../assets/custom_assertions');
var assertClip = customAssertions.assertClip;
var assertNodeDisplay = customAssertions.assertNodeDisplay;
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
var Fx = require('@src/components/fx');
var d3 = require('d3');
@@ -1110,6 +1111,28 @@ describe('A bar plot', function() {
};
}
+ checkTextTemplate([{
+ type: 'bar',
+ y: [1, 5, 3, 2],
+ text: ['A', 'B', 'C', 'D'],
+ textposition: 'inside',
+ hovertemplate: '%{text}'
+ }], 'text.bartext', [
+ ['%{text} - %{value}', ['A - 1', 'B - 5', 'C - 3', 'D - 2']],
+ [['%{y}', '%{value}', '%{text}'], ['1', '5', 'C']]
+ ]);
+
+ checkTextTemplate([{
+ type: 'bar',
+ textposition: 'outside',
+ x: ['2019-01-01', '2019-02-01'],
+ y: [1, 2],
+ hovertemplate: '%{x}',
+ texttemplate: '%{x}'
+ }], 'text.bartext', [
+ ['%{x}', ['2019-01-01', '2019-02-01']]
+ ]);
+
it('should show bar texts (inside case)', function(done) {
var data = [{
y: [10, 20, 30],
diff --git a/test/jasmine/tests/carpet_test.js b/test/jasmine/tests/carpet_test.js
index 3fada7b2e71..a71ffc3471a 100644
--- a/test/jasmine/tests/carpet_test.js
+++ b/test/jasmine/tests/carpet_test.js
@@ -13,6 +13,7 @@ var failTest = require('../assets/fail_test');
var mouseEvent = require('../assets/mouse_event');
var assertHoverLabelContent = require('../assets/custom_assertions').assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
var supplyAllDefaults = require('../assets/supply_defaults');
@@ -714,6 +715,24 @@ describe('scattercarpet hover labels', function() {
});
});
+describe('scattercarpet texttemplates', function() {
+ checkTextTemplate([{
+ 'type': 'scattercarpet',
+ 'carpet': 'carpet1',
+ 'mode': 'markers+text',
+ 'a': [0.1, 0.15, 0.25],
+ 'b': [1.5, 1.5, 1.5],
+ 'text': ['A', 'B', 'C']
+ }, {
+ 'type': 'carpet',
+ 'carpet': 'carpet1',
+ 'a': [0.1, 0.2, 0.3],
+ 'b': [1, 2, 3],
+ 'y': [[1, 2.2, 3], [1.5, 2.7, 3.5], [1.7, 2.9, 3.7]]
+ }], 'g.textpoint', [
+ ['%{text}: %{a:0.1f}, %{b:0.1f}', ['A: 0.1, 1.5', 'B: 0.1, 1.5', 'C: 0.3, 1.5']]
+ ]);
+});
describe('contourcarpet plotting & editing', function() {
var gd;
diff --git a/test/jasmine/tests/funnel_test.js b/test/jasmine/tests/funnel_test.js
index 6804069adaf..9a26e14fcb6 100644
--- a/test/jasmine/tests/funnel_test.js
+++ b/test/jasmine/tests/funnel_test.js
@@ -16,6 +16,7 @@ var rgb = color.rgb;
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
var Fx = require('@src/components/fx');
var d3 = require('d3');
@@ -1234,6 +1235,17 @@ describe('A funnel plot', function() {
.catch(failTest)
.then(done);
});
+
+ checkTextTemplate([{
+ type: 'funnel',
+ orientation: 'v',
+ x: ['A', 'B', 'C'],
+ y: [3, 2, 1],
+ textinfo: 'value+percent initial+percent previous+percent total',
+ }], 'text.bartext', [
+ ['txt: %{value}', ['txt: 3', 'txt: 2', 'txt: 1']],
+ ['%{value}-%{percentInitial}-%{percentPrevious}-%{percentTotal:0.3f}', ['3-100%-100%-0.500', '2-67%-67%-0.333', '1-33%-50%-0.167']]
+ ]);
});
describe('funnel hover', function() {
diff --git a/test/jasmine/tests/funnelarea_test.js b/test/jasmine/tests/funnelarea_test.js
index 81b6e50f492..67e2fa6a015 100644
--- a/test/jasmine/tests/funnelarea_test.js
+++ b/test/jasmine/tests/funnelarea_test.js
@@ -14,6 +14,7 @@ var rgb = require('../../../src/components/color').rgb;
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
var SLICES_SELECTOR = '.slice path';
var SLICES_TEXT_SELECTOR = '.funnelarealayer text.slicetext';
@@ -637,6 +638,18 @@ describe('Funnelarea traces', function() {
.catch(failTest)
.then(done);
});
+
+ checkTextTemplate([{
+ type: 'funnelarea',
+ values: [1, 5, 3, 2],
+ labels: ['A', 'B', 'C', 'D'],
+ text: ['textA', 'textB', 'textC', 'textD'],
+ textposition: 'inside',
+ hovertemplate: '%{text}'
+ }], 'g.slicetext', [
+ ['%{label}-%{color}-%{value}-%{percent}-%{text}', ['A-#1f77b4-1-9.09%-textA', 'B-#ff7f0e-5-45.5%-textB', 'C-#2ca02c-3-27.3%-textC', 'D-#d62728-2-18.2%-textD']],
+ [['%{label} - %{value}', '%{text}', '%{value}', '%{percent}'], ['A - 1', 'textB', '3', '18.2%']],
+ ]);
});
describe('funnelarea hovering', function() {
diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js
index 38dc37927a0..cbcdfb2ec18 100644
--- a/test/jasmine/tests/lib_test.js
+++ b/test/jasmine/tests/lib_test.js
@@ -2225,12 +2225,17 @@ describe('Test lib.js:', function() {
expect(Lib.hovertemplateString('y: %{y}', {}, locale, {y: 0}, {y: 1})).toEqual('y: 0');
});
- it('formats value using d3 mini-language', function() {
+ it('formats numbers using d3-format mini-language when `:`', function() {
expect(Lib.hovertemplateString('a: %{a:.0%}', {}, locale, {a: 0.123})).toEqual('a: 12%');
expect(Lib.hovertemplateString('a: %{a:0.2%}', {}, locale, {a: 0.123})).toEqual('a: 12.30%');
expect(Lib.hovertemplateString('b: %{b:2.2f}', {}, locale, {b: 43})).toEqual('b: 43.00');
});
+ it('formats date using d3-time-format mini-language `|`', function() {
+ expect(Lib.hovertemplateString('a: %{a|%A}', {}, locale, {a: '2019-05-22'})).toEqual('a: Wednesday');
+ expect(Lib.hovertemplateString('%{x|%b %-d, %Y}', {}, locale, {x: '2019-01-01'})).toEqual('Jan 1, 2019');
+ });
+
it('looks for default label if no format is provided', function() {
expect(Lib.hovertemplateString('y: %{y}', {yLabel: '0.1'}, locale, {y: 0.123})).toEqual('y: 0.1');
});
@@ -2245,6 +2250,34 @@ describe('Test lib.js:', function() {
}
expect(Lib.warn.calls.count()).toBe(10);
});
+
+ it('does not error out when arguments are undefined', function() {
+ expect(function() {
+ Lib.hovertemplateString('y: %{y}', undefined, locale, undefined);
+ }).not.toThrow();
+ });
+ });
+
+ describe('texttemplateString', function() {
+ var locale = false;
+ it('evaluates attributes', function() {
+ expect(Lib.texttemplateString('foo %{bar}', {}, locale, {bar: 'baz'})).toEqual('foo baz');
+ });
+
+ it('looks for default label if no format is provided', function() {
+ expect(Lib.texttemplateString('y: %{y}', {yLabel: '0.1'}, locale, {y: 0.123})).toEqual('y: 0.1');
+ });
+
+ it('warns user up to 10 times if a variable cannot be found', function() {
+ spyOn(Lib, 'warn').and.callThrough();
+ Lib.texttemplateString('%{idontexist}', {});
+ expect(Lib.warn.calls.count()).toBe(1);
+
+ for(var i = 0; i < 15; i++) {
+ Lib.texttemplateString('%{idontexist}', {});
+ }
+ expect(Lib.warn.calls.count()).toBe(11);
+ });
});
describe('relativeAttr()', function() {
diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js
index 4b6f84c8ebc..d4afa9ed524 100644
--- a/test/jasmine/tests/pie_test.js
+++ b/test/jasmine/tests/pie_test.js
@@ -14,6 +14,7 @@ var rgb = require('../../../src/components/color').rgb;
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
var SLICES_SELECTOR = '.slice path';
var SLICES_TEXT_SELECTOR = '.pielayer text.slicetext';
@@ -862,6 +863,19 @@ describe('Pie traces', function() {
.catch(failTest)
.then(done);
});
+
+ checkTextTemplate([{
+ type: 'pie',
+ values: [1, 5, 3, 2],
+ labels: ['A', 'B', 'C', 'D'],
+ text: ['textA', 'textB', 'textC', 'textD'],
+ textposition: 'inside',
+ hovertemplate: '%{text}'
+ }], 'g.slicetext', [
+ ['%{label} - %{value}', ['A - 1', 'B - 5', 'C - 3', 'D - 2']],
+ [['%{label} - %{value}', '%{text}', '%{value}', '%{percent}'], ['A - 1', 'textB', '3', '18.2%']],
+ ['%{text}-%{color}', ['textA-#d62728', 'textB-#1f77b4', 'textC-#ff7f0e', 'textD-#2ca02c']]
+ ]);
});
describe('pie hovering', function() {
diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js
index d7639333903..8c599352fd8 100644
--- a/test/jasmine/tests/scatter_test.js
+++ b/test/jasmine/tests/scatter_test.js
@@ -17,6 +17,7 @@ var assertClip = customAssertions.assertClip;
var assertNodeDisplay = customAssertions.assertNodeDisplay;
var assertMultiNodeOrder = customAssertions.assertMultiNodeOrder;
var checkEventData = require('../assets/check_event_data');
+var checkTextTemplate = require('../assets/check_texttemplate');
var constants = require('@src/traces/scatter/constants');
var supplyAllDefaults = require('../assets/supply_defaults');
@@ -1238,6 +1239,16 @@ describe('end-to-end scatter tests', function() {
.catch(failTest)
.then(done);
});
+
+ checkTextTemplate([{
+ type: 'scatter',
+ mode: 'markers+lines+text',
+ y: [1, 5, 3, 2],
+ textposition: 'top'
+ }], '.textpoint', [
+ ['%{y}', ['1', '5', '3', '2']],
+ [['%{y}', '%{x}-%{y}'], ['1', '1-5', '', '']]
+ ]);
});
describe('stacked area', function() {
diff --git a/test/jasmine/tests/scattergeo_test.js b/test/jasmine/tests/scattergeo_test.js
index 718fa6401c4..12976f642cd 100644
--- a/test/jasmine/tests/scattergeo_test.js
+++ b/test/jasmine/tests/scattergeo_test.js
@@ -12,6 +12,7 @@ var mouseEvent = require('../assets/mouse_event');
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
var failTest = require('../assets/fail_test');
var supplyAllDefaults = require('../assets/supply_defaults');
@@ -436,3 +437,24 @@ describe('scattergeo drawing', function() {
.then(done);
});
});
+
+describe('Test scattergeo texttemplate:', function() {
+ checkTextTemplate([{
+ 'type': 'scattergeo',
+ 'mode': 'markers+text',
+ 'lon': [-73.57, -79.24, -123.06],
+ 'lat': [45.5, 43.4, 49.13],
+ 'text': ['Montreal', 'Toronto', 'Vancouver']
+ }], '.scattergeo text', [
+ ['%{text}: %{lon}, %{lat}', ['Montreal: -73.57, 45.5', 'Toronto: -79.24, 43.4', 'Vancouver: -123.06, 49.13']]
+ ]);
+
+ checkTextTemplate([{
+ 'type': 'scattergeo',
+ 'mode': 'markers+text',
+ 'locations': ['Canada'],
+ 'locationmode': 'country names'
+ }], '.scattergeo text', [
+ ['%{location}', ['Canada']]
+ ]);
+});
diff --git a/test/jasmine/tests/scattergl_test.js b/test/jasmine/tests/scattergl_test.js
index fed8cb7eb0f..e08a967e028 100644
--- a/test/jasmine/tests/scattergl_test.js
+++ b/test/jasmine/tests/scattergl_test.js
@@ -8,6 +8,7 @@ var destroyGraphDiv = require('../assets/destroy_graph_div');
var failTest = require('../assets/fail_test');
var delay = require('../assets/delay');
var readPixel = require('../assets/read_pixel');
+var checkTextTemplate = require('../assets/check_texttemplate');
describe('end-to-end scattergl tests', function() {
var gd;
@@ -37,6 +38,17 @@ describe('end-to-end scattergl tests', function() {
}).catch(failTest).then(done);
});
+ checkTextTemplate([{
+ type: 'scattergl',
+ mode: 'text+lines',
+ x: [1, 2, 3, 4],
+ y: [2, 3, 4, 5],
+ text: ['A', 'B', 'C', 'D'],
+ }], '@gl', [
+ ['%{text}: %{x}, %{y}', ['A: 1, 2', 'B: 2, 3', 'C: 3, 4', 'D: 4, 5']],
+ [['%{x}', '%{x}', '%{text}', '%{y}'], ['1', '2', 'C', '5']]
+ ]);
+
it('@gl should update a plot with text labels', function(done) {
Plotly.react(gd, [{
type: 'scattergl',
@@ -105,23 +117,25 @@ describe('end-to-end scattergl tests', function() {
}).catch(failTest).then(done);
});
- it('@gl should handle a plot with less text labels than data points', function(done) {
- expect(function() {
- var mock = {
- 'type': 'scattergl',
- 'mode': 'markers+text',
- 'x': [3, 2, 1, 0],
- 'y': [0, 1, 4, 9],
- 'textposition': 'top center'
- };
- mock.text = ['1', '2', '3'];
- Plotly.plot(gd, [mock])
- .then(function() {
- expect(mock.text.length).toBe(3);
- })
- .catch(failTest);
- }).not.toThrow();
- done();
+ ['text', 'texttemplate'].forEach(function(attr) {
+ it('@gl should handle a plot with less ' + attr + ' labels than data points', function(done) {
+ expect(function() {
+ var mock = {
+ 'type': 'scattergl',
+ 'mode': 'markers+text',
+ 'x': [3, 2, 1, 0],
+ 'y': [0, 1, 4, 9],
+ 'textposition': 'top center'
+ };
+ mock[attr] = ['1', '2', '3'];
+ Plotly.plot(gd, [mock])
+ .then(function() {
+ expect(mock[attr].length).toBe(3);
+ })
+ .catch(failTest);
+ }).not.toThrow();
+ done();
+ });
});
it('@gl should be able to toggle visibility', function(done) {
diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js
index 701306dc6bb..51839f51af7 100644
--- a/test/jasmine/tests/scattermapbox_test.js
+++ b/test/jasmine/tests/scattermapbox_test.js
@@ -139,7 +139,7 @@ describe('scattermapbox convert', function() {
Plots.doCalcdata(gd, fullTrace);
var calcTrace = gd.calcdata[0];
- return convert(calcTrace);
+ return convert({_fullLayout: {_d3locale: false}}, calcTrace);
}
function assertVisibility(opts, expectations) {
@@ -478,6 +478,44 @@ describe('scattermapbox convert', function() {
expect(actualText).toEqual(['A', 'B', 'C', 'F', undefined]);
});
+ it('should generate correct output for texttemplate without text', function() {
+ var opts = _convert(Lib.extendFlat({}, base, {
+ mode: 'lines+text',
+ connectgaps: true,
+ textposition: 'outside',
+ texttemplate: ['A', 'B', 'C', 'D', 'E', 'F']
+ }));
+
+ var actualText = opts.symbol.geojson.features.map(function(f) {
+ return f.properties.text;
+ });
+
+ expect(actualText).toEqual(['A', 'B', 'C', 'F', '']);
+ });
+
+ it('should generate correct output for texttemplate', function() {
+ var mock = {
+ 'type': 'scattermapbox',
+ 'mode': 'markers+text',
+ 'lon': [-73.57, -79.24, -123.06],
+ 'lat': [45.5, 43.4, 49.13],
+ 'text': ['Montreal', 'Toronto', 'Vancouver'],
+ 'texttemplate': '%{text} (%{lon}, %{lat}): %{customdata:.2s}',
+ 'textposition': 'top center',
+ 'customdata': [1780000, 2930000, 675218]
+ };
+ var opts = _convert(mock);
+ var actualText = opts.symbol.geojson.features.map(function(f) {
+ return f.properties.text;
+ });
+
+ expect(actualText).toEqual([
+ 'Montreal (-73.57, 45.5): 1.8M',
+ 'Toronto (-79.24, 43.4): 2.9M',
+ 'Vancouver (-123.06, 49.13): 680k'
+ ]);
+ });
+
it('should generate correct output for lines traces with trailing gaps', function() {
var opts = _convert(Lib.extendFlat({}, base, {
mode: 'lines',
diff --git a/test/jasmine/tests/scatterpolar_test.js b/test/jasmine/tests/scatterpolar_test.js
index 69d26588719..494a893db5f 100644
--- a/test/jasmine/tests/scatterpolar_test.js
+++ b/test/jasmine/tests/scatterpolar_test.js
@@ -9,6 +9,7 @@ var mouseEvent = require('../assets/mouse_event');
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
describe('Test scatterpolar trace defaults:', function() {
var traceOut;
@@ -198,3 +199,17 @@ describe('Test scatterpolar hover:', function() {
});
});
});
+
+describe('Test scatterpolar texttemplate:', function() {
+ checkTextTemplate([{
+ 'type': 'scatterpolar',
+ 'mode': 'markers+text',
+ 'text': ['A', 'B', 'C'],
+ 'textposition': 'top center',
+ 'r': [1, 0.5, 1],
+ 'theta': [0, 90, 180],
+ }], 'g.textpoint', [
+ ['%{text}: (%{r:0.2f}, %{theta:0.1f})', ['A: (1.00, 0.0)', 'B: (0.50, 90.0)', 'C: (1.00, 180.0)']],
+ [['', 'b%{theta:0.2f}', '%{theta:0.2f}'], ['', 'b90.00', '180.00']]
+ ]);
+});
diff --git a/test/jasmine/tests/scatterpolargl_test.js b/test/jasmine/tests/scatterpolargl_test.js
index 5e6d2f2866e..6afd16c50dd 100644
--- a/test/jasmine/tests/scatterpolargl_test.js
+++ b/test/jasmine/tests/scatterpolargl_test.js
@@ -11,6 +11,7 @@ var readPixel = require('../assets/read_pixel');
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
describe('Test scatterpolargl hover:', function() {
var gd;
@@ -377,3 +378,17 @@ describe('Test scatterpolargl autorange:', function() {
});
});
});
+
+describe('Test scatterpolargl texttemplate:', function() {
+ checkTextTemplate([{
+ 'type': 'scatterpolargl',
+ 'mode': 'markers+text',
+ 'text': ['A', 'B', 'C'],
+ 'textposition': 'top center',
+ 'r': [1, 0.5, 1],
+ 'theta': [0, 90, 180],
+ }], 'g.textpoint', [
+ ['%{text}: (%{r:0.2f}, %{theta:0.1f})', ['A: (1.00, 0.0)', 'B: (0.50, 90.0)', 'C: (1.00, 180.0)']],
+ [['', 'b%{theta:0.2f}', '%{theta:0.2f}'], ['', 'b90.00', '180.00']]
+ ]);
+});
diff --git a/test/jasmine/tests/scatterternary_test.js b/test/jasmine/tests/scatterternary_test.js
index 0cabd822e5d..83299d8027b 100644
--- a/test/jasmine/tests/scatterternary_test.js
+++ b/test/jasmine/tests/scatterternary_test.js
@@ -11,6 +11,7 @@ var supplyAllDefaults = require('../assets/supply_defaults');
var mouseEvent = require('../assets/mouse_event');
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
var assertClip = customAssertions.assertClip;
var assertNodeDisplay = customAssertions.assertNodeDisplay;
@@ -554,3 +555,16 @@ describe('Test scatterternary *cliponaxis*', function() {
.then(done);
});
});
+
+describe('Test scatterternary texttemplate:', function() {
+ checkTextTemplate([{
+ 'type': 'scatterternary',
+ 'a': [3, 2, 5],
+ 'b': [2, 5, 2],
+ 'c': [5, 2, 2 ],
+ 'mode': 'markers+text',
+ 'text': ['A', 'B', 'C']
+ }], 'g.textpoint', [
+ ['%{text} (%{a:.1f}, %{b:.1f}, %{c:.1f})', ['A (3.0, 2.0, 5.0)', 'B (2.0, 5.0, 2.0)', 'C (5.0, 2.0, 2.0)']]
+ ]);
+});
diff --git a/test/jasmine/tests/sunburst_test.js b/test/jasmine/tests/sunburst_test.js
index a6ce493d67f..cce6b6034ff 100644
--- a/test/jasmine/tests/sunburst_test.js
+++ b/test/jasmine/tests/sunburst_test.js
@@ -14,6 +14,7 @@ var failTest = require('../assets/fail_test');
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
function _mouseEvent(type, gd, v) {
return function() {
@@ -1237,3 +1238,17 @@ describe('Test sunburst interactions edge cases', function() {
.then(done);
});
});
+
+describe('Test sunburst texttemplate:', function() {
+ checkTextTemplate([{
+ type: 'sunburst',
+ labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Esther'],
+ values: [11, 12, 13, 14, 15],
+ text: ['1', '2', '3', '4', '5'],
+ parents: ['', 'Eve', 'Eve', 'Seth', 'Seth' ]
+ }], 'g.slicetext', [
+ ['txt: %{label}', ['txt: Eve', 'txt: Cain', 'txt: Seth', 'txt: Enos', 'txt: Esther']],
+ [['txt: %{label}', '%{text}', 'value: %{value}'], ['txt: Eve', '2', 'value: 13', '', '']],
+ ['%{color}', ['rgba(0,0,0,0)', '#1f77b4', '#ff7f0e', '#1f77b4', '#1f77b4']]
+ ]);
+});
diff --git a/test/jasmine/tests/waterfall_test.js b/test/jasmine/tests/waterfall_test.js
index 20845b600f2..f9a35962863 100644
--- a/test/jasmine/tests/waterfall_test.js
+++ b/test/jasmine/tests/waterfall_test.js
@@ -16,6 +16,7 @@ var rgb = color.rgb;
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
+var checkTextTemplate = require('../assets/check_texttemplate');
var Fx = require('@src/components/fx');
var d3 = require('d3');
@@ -1208,6 +1209,18 @@ describe('A waterfall plot', function() {
.catch(failTest)
.then(done);
});
+
+ checkTextTemplate([{
+ type: 'waterfall',
+ y: [1, 5, 3, 2],
+ text: ['A', 'B', 'C', 'D'],
+ textposition: 'inside',
+ hovertemplate: '%{text}'
+ }], 'text.bartext', [
+ ['%{value}', ['1', '6', '9', '11']],
+ ['%{initial} %{delta} %{final}', ['0 1 1', '1 5 6', '6 3 9', '9 2 11']],
+ [['%{y}', '%{value}', '%{text}', '%{label}'], ['1', '6', 'C', '3']]
+ ]);
});
describe('waterfall visibility toggling:', function() {