Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

texttemplate with date formatting #4071

Merged
merged 43 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8deaed1
texttemplate: initial implementation
antoinerg Jul 22, 2019
ce3c9cc
move texttemplate_attributes.js from fx to plots
antoinerg Jul 23, 2019
ee0a309
do not export `templateFormatString`
antoinerg Jul 23, 2019
9330290
texttemplate: test that textinfo is not coerced when it's defined
antoinerg Jul 23, 2019
cf826dd
texttemplate: update description
antoinerg Jul 23, 2019
c3e9961
set texttemplate dflt back to ''
antoinerg Jul 23, 2019
e0bde45
texttemplate: add customdata variable and :lock: down with test
antoinerg Jul 23, 2019
902d2d3
texttemplate: add support for scatter(carpet|geo|polar|ternary)
antoinerg Jul 24, 2019
0b516f7
texttemplate: use result of appendArrayPointValue as available variables
antoinerg Jul 24, 2019
7d21680
texttemplate: fix scattergeo variable names
antoinerg Jul 24, 2019
7fa4dc5
texttemplate: add support for scattergl
antoinerg Jul 24, 2019
aeb6d1f
texttemplate: add support for scatterpolargl
antoinerg Aug 6, 2019
f9062e4
texttemplate: add support for scatter3d
antoinerg Aug 6, 2019
3ad30ab
Merge branch 'master' into texttemplate
antoinerg Aug 6, 2019
2d50269
Merge branch 'texttemplate-gl' into texttemplate
antoinerg Aug 6, 2019
05f4722
check_texttemplate: put @gl tests in its own `it` block
antoinerg Aug 6, 2019
6729286
split gl2d and gl3d in separate mocks
antoinerg Aug 6, 2019
cbbcfcb
texttemplate: use d3locale in GL traces, fix style
antoinerg Aug 7, 2019
11ea019
Merge branch 'texttemplate-split' into texttemplate
antoinerg Aug 7, 2019
32498f8
remove unused baseline `texttemplate_2`
antoinerg Aug 7, 2019
7bb3379
texttemplate: text in legends should not changed
antoinerg Aug 13, 2019
9677929
texttemplate: fix GL traces with txt array longer than data
antoinerg Aug 13, 2019
b9188a9
Merge branch 'master' into texttemplate
antoinerg Aug 13, 2019
decec45
scattergl: :lock: down handling of texttemplate with fewer elements t…
antoinerg Aug 13, 2019
2f6bf8a
scattergl: make sure text(template) is not mutated
antoinerg Aug 13, 2019
e7c034a
texttemplate: test that trace.meta is passed to template
antoinerg Aug 14, 2019
4b1face
texttemplate: pass trace._meta to gl2d traces
antoinerg Aug 14, 2019
2528aa6
Merge branch 'master' into texttemplate
antoinerg Aug 14, 2019
e381c25
Merge branch 'texttemplate-meta' into texttemplate
antoinerg Aug 14, 2019
888cf68
texttemplate: DRY code for gl(2|3)d traces, pass meta to scatter3d
antoinerg Aug 14, 2019
7d3fabb
texttemplate: add support for scattermapbox
antoinerg Aug 7, 2019
b2df623
funnelarea: fix description of variables in (hover|text)template
antoinerg Aug 20, 2019
8c339df
funnel(area): fix description of variables for (hover|text)template
antoinerg Aug 21, 2019
c6370d9
support formatting date in (hover|text)template
antoinerg Aug 19, 2019
836d5a2
templateFormatString: use d3locale.timeFormat if available fot date fmt
antoinerg Aug 20, 2019
3dda912
texttemplate for scattermapbox: fix editType, improve style and mock
antoinerg Aug 26, 2019
354b70f
fix baseline "mapbox_texttemplate"
antoinerg Aug 27, 2019
4ef8392
merge (text|hover)templateAttrs() into a single file
antoinerg Aug 27, 2019
e700fc2
texttemplate: fix style
antoinerg Aug 27, 2019
6780a3d
texttemplate: do not coerce texttemplate when no text is displayed
antoinerg Aug 28, 2019
db87e62
(hover|text)template: pie/funnel/waterfall fix attribute definition
antoinerg Aug 28, 2019
daa2c7e
templateFormatString: do not error out when lookup objects are undefined
antoinerg Aug 28, 2019
c7dd845
don't coerce texttemplate when no text - fix for scatterternary
etpinard Aug 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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 ?
Expand Down
2 changes: 1 addition & 1 deletion src/components/legend/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
61 changes: 43 additions & 18 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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*$/;

/**
Expand Down Expand Up @@ -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.
Expand All @@ -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 = {};
Expand All @@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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',
Expand All @@ -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,
Expand All @@ -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;
};
6 changes: 5 additions & 1 deletion src/traces/bar/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/traces/bar/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
74 changes: 69 additions & 5 deletions src/traces/bar/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -555,6 +560,65 @@ function getTextPosition(trace, index) {
return helpers.coerceEnumerated(attributeTextPosition, value);
}

function calcTexttemplate(fullLayout, calcTrace, index, xa, ya) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to merge this routine with calcTextinfo where we could first set the obj.??Label values and then use those to translate the textinfo flags.

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);
etpinard marked this conversation as resolved.
Show resolved Hide resolved

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');
Expand Down
2 changes: 1 addition & 1 deletion src/traces/barpolar/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Loading