diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js
index 0f793e5c56f..dfb0647e0cd 100644
--- a/src/components/annotations/draw.js
+++ b/src/components/annotations/draw.js
@@ -209,9 +209,13 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
var font = options.font;
+ var text = fullLayout.meta ?
+ Lib.templateString(options.text, {meta: fullLayout.meta}) :
+ options.text;
+
var annText = annTextGroupInner.append('text')
.classed('annotation-text', true)
- .text(options.text);
+ .text(text);
function textLayout(s) {
s.call(Drawing.font, font)
@@ -678,6 +682,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
.call(textLayout)
.on('edit', function(_text) {
options.text = _text;
+
this.call(textLayout);
modifyItem('text', _text);
diff --git a/src/components/titles/index.js b/src/components/titles/index.js
index 626570d0568..d761382ee8e 100644
--- a/src/components/titles/index.js
+++ b/src/components/titles/index.js
@@ -97,6 +97,10 @@ function draw(gd, titleClass, options) {
if(!editable) txt = '';
}
+ if(fullLayout.meta) {
+ txt = Lib.templateString(txt, {meta: fullLayout.meta});
+ }
+
var elShouldExist = txt || editable;
if(!group) {
diff --git a/src/plots/gl3d/layout/convert.js b/src/plots/gl3d/layout/convert.js
index 8d8649f46a7..a065203fcb4 100644
--- a/src/plots/gl3d/layout/convert.js
+++ b/src/plots/gl3d/layout/convert.js
@@ -6,10 +6,10 @@
* LICENSE file in the root directory of this source tree.
*/
-
'use strict';
var str2RgbaArray = require('../../../lib/str2rgbarray');
+var Lib = require('../../../lib');
var AXES_NAMES = ['xaxis', 'yaxis', 'zaxis'];
@@ -66,7 +66,7 @@ function AxesOptions() {
var proto = AxesOptions.prototype;
-proto.merge = function(sceneLayout) {
+proto.merge = function(fullLayout, sceneLayout) {
var opts = this;
for(var i = 0; i < 3; ++i) {
var axes = sceneLayout[AXES_NAMES[i]];
@@ -83,7 +83,10 @@ proto.merge = function(sceneLayout) {
}
// Axes labels
- opts.labels[i] = axes.title.text;
+ opts.labels[i] = fullLayout.meta ?
+ Lib.templateString(axes.title.text, {meta: fullLayout.meta}) :
+ axes.title.text;
+
if('font' in axes.title) {
if(axes.title.font.color) opts.labelColor[i] = str2RgbaArray(axes.title.font.color);
if(axes.title.font.family) opts.labelFont[i] = axes.title.font.family;
@@ -151,9 +154,9 @@ proto.merge = function(sceneLayout) {
};
-function createAxesOptions(plotlyOptions) {
+function createAxesOptions(fullLayout, sceneLayout) {
var result = new AxesOptions();
- result.merge(plotlyOptions);
+ result.merge(fullLayout, sceneLayout);
return result;
}
diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js
index bb3dea88c8c..a0a0e571d9c 100644
--- a/src/plots/gl3d/scene.js
+++ b/src/plots/gl3d/scene.js
@@ -305,7 +305,7 @@ function Scene(options, fullLayout) {
/*
* Move this to calc step? Why does it work here?
*/
- this.axesOptions = createAxesOptions(fullLayout[this.id]);
+ this.axesOptions = createAxesOptions(fullLayout, fullLayout[this.id]);
this.spikeOptions = createSpikeOptions(fullLayout[this.id]);
this.container = sceneContainer;
this.staticMode = !!options.staticPlot;
@@ -406,7 +406,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
this.fullSceneLayout = fullSceneLayout;
this.glplotLayout = fullSceneLayout;
- this.axesOptions.merge(fullSceneLayout);
+ this.axesOptions.merge(fullLayout, fullSceneLayout);
this.spikeOptions.merge(fullSceneLayout);
// Update camera and camera mode
diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js
index 7a0090b03af..521c32b7763 100644
--- a/src/plots/layout_attributes.js
+++ b/src/plots/layout_attributes.js
@@ -409,6 +409,17 @@ module.exports = {
},
editType: 'modebar'
},
+ meta: {
+ valType: 'data_array',
+ editType: 'plot',
+ description: [
+ 'Assigns extra meta information that can be used in various `text` attributes.',
+ 'Attributes such as the graph, axis and colorbar `title.text` and annotation `text`',
+ 'support `meta`. One can access `meta` fields using template strings:',
+ '`%{meta[i]}` where `i` is the index of the `meta`',
+ 'item in question.'
+ ].join(' ')
+ },
_deprecated: {
title: {
valType: 'string',
diff --git a/src/plots/plots.js b/src/plots/plots.js
index e133c617229..5e262f3ffab 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -1455,6 +1455,8 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
coerce('modebar.activecolor', Color.addOpacity(modebarDefaultColor, 0.7));
coerce('modebar.uirevision', uirevision);
+ coerce('meta');
+
Registry.getComponentMethod(
'calendars',
'handleDefaults'
diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js
index 8a61f7ccfd0..10524a40894 100644
--- a/src/traces/pie/plot.js
+++ b/src/traces/pie/plot.js
@@ -334,7 +334,11 @@ module.exports = function plot(gd, cdpie) {
s.attr('data-notex', 1);
});
- titleText.text(trace.title.text)
+ var txt = fullLayout.meta ?
+ Lib.templateString(trace.title.text, {meta: fullLayout.meta}) :
+ trace.title.text;
+
+ titleText.text(txt)
.attr({
'class': 'titletext',
transform: '',
@@ -467,6 +471,8 @@ function determineInsideTextFont(trace, pt, layoutFont) {
}
function prerenderTitles(cdpie, gd) {
+ var fullLayout = gd._fullLayout;
+
var cd0, trace;
// Determine the width and height of the title for each pie.
for(var i = 0; i < cdpie.length; i++) {
@@ -474,9 +480,13 @@ function prerenderTitles(cdpie, gd) {
trace = cd0.trace;
if(trace.title.text) {
+ var txt = fullLayout.meta ?
+ Lib.templateString(trace.title.text, {meta: fullLayout.meta}) :
+ trace.title.text;
+
var dummyTitle = Drawing.tester.append('text')
.attr('data-notex', 1)
- .text(trace.title.text)
+ .text(txt)
.call(Drawing.font, trace.title.font)
.call(svgTextUtils.convertToTspans, gd);
var bBox = Drawing.bBox(dummyTitle.node(), true);
diff --git a/test/image/baselines/layout_metatext.png b/test/image/baselines/layout_metatext.png
new file mode 100644
index 00000000000..e8deb7bc512
Binary files /dev/null and b/test/image/baselines/layout_metatext.png differ
diff --git a/test/image/mocks/layout_metatext.json b/test/image/mocks/layout_metatext.json
new file mode 100644
index 00000000000..d24c9040219
--- /dev/null
+++ b/test/image/mocks/layout_metatext.json
@@ -0,0 +1,85 @@
+{
+ "data": [{
+ "y": [1, 2, 1]
+ }, {
+ "type": "scatterpolar",
+ "r": [1, 2, 1]
+ }, {
+ "type": "scatterternary",
+ "a": [2, 1, 1],
+ "b": [1, 2, 1],
+ "c": [1, 1, 2.12]
+ }, {
+ "type": "surface",
+ "z": [[1, 2, 3], [1, 2, 1], [3, 2, 1]],
+ "colorbar": {
+ "title": {"text": "Product %{meta[0]}", "side": "right"},
+ "len": 0.3
+ }
+ }, {
+ "type": "pie",
+ "labels": ["a", "b", "c"],
+ "values": [1, 2, 3],
+ "domain": {"row": 0, "column": 1},
+ "title": {"text": "Employee %{meta[1]}"}
+ }],
+ "layout": {
+ "meta": ["A", "B", "IMPORTANT", "companyTM", 1000],
+ "grid": {"rows": 3, "columns": 2, "xgap": 0.2, "ygap": 0.35},
+ "width": 700,
+ "height": 800,
+ "margin": {"b": 40},
+ "showlegend": false,
+
+ "title": {"text": "This graph is %{meta[2]}"},
+ "xaxis": {
+ "domain": {"row": 0, "column": 0},
+ "title": {"text": "Worth more than %{meta[4]} %{meta[1]}"}
+ },
+ "yaxis": {
+ "domain": {"row": 0, "column": 0},
+ "title": {"text": "$ by %{meta[3]}"}
+ },
+ "polar": {
+ "bgcolor": "#d3d3d3",
+ "domain": {"row": 1, "column": 0},
+ "radialaxis": {
+ "title": {
+ "text": "%{meta[3]} -> %{meta[4]}",
+ "font": {"color": "red"}
+ }
+ }
+ },
+ "ternary": {
+ "domain": {"row": 2, "column": 0},
+ "aaxis": {"title": {"text": "%{meta[2]}"}},
+ "baxis": {"title": {"text": "%{meta[1]}"}},
+ "caxis": {"title": {"text": "%{meta[4]}"}}
+ },
+ "scene": {
+ "domain": {"row": 1, "column": 1},
+ "camera": {"eye": {"x": 0.01, "y": 0.01, "z": 2.165}},
+ "xaxis": {"title": {"text": "AX:%{meta[1]}"}},
+ "yaxis": {"title": {"text": "AX:%{meta[0]}"}},
+ "zaxis": {"title": {"text": "%{meta[1]}%{meta[0]}"}},
+ "annotations": [{
+ "text": "Look at %{meta[1]}",
+ "bgcolor": "#d3d3d3",
+ "borderpad": 2,
+ "bordercolor": "#000",
+ "borderwidth": "1",
+ "x": 1,
+ "y": 1,
+ "z": 2
+ }]
+ },
+
+ "annotations": [{
+ "text": "N.B. %{meta[2]}",
+ "xref": "x",
+ "yref": "y",
+ "x": 1,
+ "y": 2
+ }]
+ }
+}