Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e980e44
Linting/formatting
camdecoster Sep 29, 2025
212f892
Return empty string for undefined value in templateFormatString
camdecoster Sep 29, 2025
75b157f
Refactoring
camdecoster Oct 2, 2025
5f3670f
Add fallback value for template strings
camdecoster Oct 3, 2025
336e9ae
Add fallback to calls to hovertemplateString
camdecoster Oct 3, 2025
02a4cad
Add fallback to calls to texttemplateString
camdecoster Oct 3, 2025
77bc2b9
Add fallback to calls to texttemplateStringForShapes
camdecoster Oct 7, 2025
85b67e9
Update defaults calculations
camdecoster Oct 6, 2025
680c793
Add helper function for template fallback attributes
camdecoster Oct 6, 2025
52c29cc
Add fallback value to attributes files
camdecoster Oct 7, 2025
f623b22
Update esbuild strip meta plugin to handle more joined arrays
camdecoster Oct 8, 2025
074f8a0
Return array from ternary
camdecoster Oct 8, 2025
6f3daa8
Update tests per default fallback value
camdecoster Oct 8, 2025
6719bd3
Update schema
camdecoster Oct 8, 2025
84fc044
Update test baselines
camdecoster Oct 8, 2025
e719f74
Rename object keys
camdecoster Oct 8, 2025
fb7a40c
Add/update tests to check fallback value
camdecoster Oct 9, 2025
faaae28
Add draftlog
camdecoster Oct 9, 2025
5c032c6
Fix typos
camdecoster Oct 21, 2025
a0ce641
Update fallback `editType` to match template
camdecoster Oct 21, 2025
14ab31c
Handle undefined values and missing values differently
camdecoster Oct 23, 2025
4f79efe
Update default fallback value and template attribute descriptions
camdecoster Oct 23, 2025
257475c
Add tests for missing and undefined values
camdecoster Oct 23, 2025
a148edd
Update schema
camdecoster Oct 23, 2025
c40fe9f
Merge remote-tracking branch 'origin/master' into cam/7564/return-emp…
camdecoster Oct 23, 2025
6a66148
Revert "Update test baselines"
camdecoster Oct 23, 2025
5215f13
Always use fallback for missing values, except when fallback is false
camdecoster Oct 24, 2025
30f87f3
Update schema
camdecoster Oct 24, 2025
b61d70d
Update mocks to show fallback values
camdecoster Oct 24, 2025
d900ca3
Update tests per final behavior
camdecoster Oct 24, 2025
a061787
Update baseline images
camdecoster Oct 24, 2025
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
20 changes: 8 additions & 12 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/;
*
* @param {object} options - Configuration object
* @param {array} options.data - Data objects containing substitution values
* @param {string} options.fallback - Fallback value when substitution fails
* @param {boolean|string} options.fallback - Fallback value when substitution fails. If false, the specifier is used.
* @param {object} options.labels - Data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'}
* @param {object} options.locale - D3 locale for formatting
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: IMO this argument should continue to be called d3locale for clarity

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm going to leave it as is since the JSDoc description explains what it is.

* @param {object} options.opts - Additional options
Expand Down Expand Up @@ -1151,47 +1151,43 @@ function templateFormatString({ data = [], locale, fallback, labels = {}, opts,
parsedNumber = _match.number;
}

let keyIsMissing = true;
let value = undefined;
if (hasOther) {
// 'other' specifiers that are undefined return an empty string by design
if (labels[key] === undefined) return '';
value = labels[key];
keyIsMissing = false;
} else {
for (const obj of data) {
if (!obj) continue;
if (obj.hasOwnProperty(key)) {
value = obj[key];
keyIsMissing = false;
break;
}

if (!SIMPLE_PROPERTY_REGEX.test(key)) {
// true here means don't convert null to undefined
value = lib.nestedProperty(obj, key).get(true);
keyIsMissing = false;
}
if (value !== undefined) break;
}
}

if (keyIsMissing) {
if (value === undefined) {
const { count, max, name } = opts;
if (count < max)
const fallbackValue = fallback === false ? match : fallback;
if (count < max) {
lib.warn(
[
`Variable '${key}' in ${name} could not be found!`,
'Please verify that the template is correct.'
'Please verify that the template is correct.',
`Using value: '${fallbackValue}'.`
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
`Using value: '${fallbackValue}'.`
fallback === false ? '' : `Using fallback value: '${fallbackValue}'.`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As we discussed, I'm going to leave this as is since it clarifies what the variable is being replaced with.

].join(' ')
);
}
if (count === max) lib.warn(`Too many '${name}' warnings - additional warnings will be suppressed.`);
opts.count++;

return match;
} else if (value === undefined) {
// In this case, the actual value in the data set is 'undefined', so use fallback without warning
return fallback;
return fallbackValue;
}

if (parsedOp === '*') value *= parsedNumber;
Expand Down
7 changes: 5 additions & 2 deletions src/plots/template_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,11 @@ exports.shapeTexttemplateAttrs = ({ editType = 'arraydraw', newshape } = {}, ext
});

exports.templatefallbackAttrs = ({ editType = 'none' } = {}) => ({
Copy link
Contributor

@emilykl emilykl Oct 14, 2025

Choose a reason for hiding this comment

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

Need to set the correct editType here, I think it should be calc (or possibly plot?)

Reference for meaning of different editType values

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After reviewing the changes again, I think it should be 'none' for hovertemplatefallback, 'calc' for texttemplatefallback, and 'arraydraw' for the shape text template fallback. I'll go through and double check all of them.

valType: 'string',
valType: 'any',
dflt: '-',
editType,
description: "Fallback value that's displayed when a variable referenced in a template has an undefined value."
description: [
Copy link
Contributor

Choose a reason for hiding this comment

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

@camdecoster nit: these should be single-quoted strings – does the Biome formatter enforce that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Double quotes are okay in this case because single quotes are used within the strings: that's and 'false'. This way no quotes need to be escaped.

"Fallback string that's displayed when a variable referenced in a template is missing.",
"If the boolean value 'false' is passed in, the specifier with the missing variable will be displayed."
].join(' ')
});
Binary file modified test/image/baselines/icicle_with-without_values_template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/text_on_shapes_texttemplate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/image/mocks/icicle_with-without_values_template.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{
"type": "icicle",
"texttemplate": "<sup>%{currentPath}<b>%{label}</b></sup><br><b>%{text}</b><br>%{value}<br>%{parent} <=> %{percentParent}<br>%{entry} <=> %{percentEntry}<br>%{root} <=> %{percentRoot}",
"texttemplatefallback": "Scruffy, the Janitor",
"hovertemplate": "<sup>%{currentPath}<b>%{label}</b></sup><br><b>%{text}</b><br>%{value}<br>%{parent} <=> %{percentParent}<br>%{entry} <=> %{percentEntry}<br>%{root} <=> %{percentRoot}",
"name": "without values",
"level": "Oscar",
Expand Down
1 change: 1 addition & 0 deletions test/image/mocks/treemap_with-without_values_template.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{
"type": "treemap",
"texttemplate": "<sup>%{currentPath}<b>%{label}</b></sup><br><b>%{text}</b><br>%{value}<br>%{parent} <=> %{percentParent}<br>%{entry} <=> %{percentEntry}<br>%{root} <=> %{percentRoot}",
"texttemplatefallback": false,
"hovertemplate": "<sup>%{currentPath}<b>%{label}</b></sup><br><b>%{text}</b><br>%{value}<br>%{parent} <=> %{percentParent}<br>%{entry} <=> %{percentEntry}<br>%{root} <=> %{percentRoot}",
"name": "without values",
"level": "Oscar",
Expand Down
6 changes: 3 additions & 3 deletions test/jasmine/tests/icicle_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ describe('Test icicle texttemplate without `values` should work at root level:',
[
'%{percentParent} of %{parent}',
[
' of ',
'- of -',
'100% of Seth',
'33% of Eve',
'17% of Eve',
Expand All @@ -957,7 +957,7 @@ describe('Test icicle texttemplate without `values` should work at root level:',
[
'label: Eve',
'text: fourteen',
'value: ', // N.B. there is no `values` array
'value: -', // N.B. there is no `values` array
'17% of Eve',
'17% of Eve',
'17% of Eve',
Expand Down Expand Up @@ -1085,7 +1085,7 @@ describe('Test icicle texttemplate with *total* `values` should work at root lev
[
'%{percentParent} of %{parent}',
[
' of ',
'- of -',
'22% of Eve',
'18% of Eve',
'9% of Eve',
Expand Down
12 changes: 6 additions & 6 deletions test/jasmine/tests/lib_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2706,18 +2706,18 @@ describe('Test lib.js:', function () {
});

// This test must come after the warning count since it will affect the count
it('replaces template specifiers where the key is missing with the specifier', () => {
it('replaces template specifiers with the specifier when fallback is false', () => {
expect(
Lib.hovertemplateString({
data: [{ group: 1 }],
fallback: 'Planet Express',
fallback: false,
template: 'foo %{group} %{trace}'
})
).toEqual('foo 1 %{trace}');
});

// This test must come after the warning count since it will affect the count
it('replaces template specifiers where the value is undefined with the fallback value', () => {
it('replaces template specifiers with the fallback value when fallback is provided', () => {
expect(
Lib.hovertemplateString({
data: [{ group: 1, trace: undefined }],
Expand Down Expand Up @@ -2773,18 +2773,18 @@ describe('Test lib.js:', function () {
});

// This test must come after the warning count since it will affect the count
it('replaces template specifiers where the key is missing with the specifier', () => {
it('replaces template specifiers with the specifier when fallback is false', () => {
expect(
Lib.texttemplateString({
data: [{ group: 1 }],
fallback: 'Zoidberg',
fallback: false,
template: 'foo %{group} %{trace}'
})
).toEqual('foo 1 %{trace}');
});

// This test must come after the warning count since it will affect the count
it('replaces template specifiers where the value is undefined with the fallback value', () => {
it('replaces template specifiers with the fallback value when fallback is provided', () => {
expect(
Lib.texttemplateString({
data: [{ group: 1, trace: undefined }],
Expand Down
8 changes: 4 additions & 4 deletions test/jasmine/tests/sunburst_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2293,7 +2293,7 @@ describe('Test sunburst texttemplate without `values` should work at root level:
[
'%{percentParent} of %{parent}',
[
' of ',
'- of -',
'100% of Seth',
'33% of Eve',
'17% of Eve',
Expand All @@ -2319,7 +2319,7 @@ describe('Test sunburst texttemplate without `values` should work at root level:
[
'label: Eve',
'text: fourteen',
'value: ', // N.B. there is no `values` array
'value: -', // N.B. there is no `values` array
'17% of Eve',
'17% of Eve',
'17% of Eve',
Expand Down Expand Up @@ -2447,7 +2447,7 @@ describe('Test sunburst texttemplate with *total* `values` should work at root l
[
'%{percentParent} of %{parent}',
[
' of ',
'- of -',
'22% of Eve',
'18% of Eve',
'9% of Eve',
Expand Down Expand Up @@ -2601,7 +2601,7 @@ describe('Test sunburst texttemplate with *remainder* `values` should work at ro
[
'%{percentParent} of %{parent}',
[
' of ',
'- of -',
'20% of Eve',
'12% of Eve',
'6% of Eve',
Expand Down
2 changes: 1 addition & 1 deletion test/jasmine/tests/treemap_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1937,7 +1937,7 @@ describe('Test treemap texttemplate without `values` should work:', function ()
'Eve',
'label: Cain, text: fourteen',
'Seth',
'value: ', // N.B. there is no `values` array
'value: -', // N.B. there is no `values` array
'17% of Eve',
'17% of Eve',
'Awan',
Expand Down
Loading