Skip to content

Conversation

@basbroek
Copy link
Contributor

@basbroek basbroek commented Jul 24, 2025

PR Description

This PR adds support for rendering newlines in tooltips and potentially closes #7564.

A new function has been added in the tooltip encoder that transforms tooltip values that are arrays into strings joined by newline characters. This allows multi-line tooltip content to be displayed correctly.

To support this behavior visually, a small CSS update is required in the vega-tooltip package — see the accompanying PR in that repository.

Example

With the new code, this example spec (open in editor) will render a tooltip like this:

example-multiline-tooltip

@basbroek basbroek requested a review from a team as a code owner July 24, 2025 15:13
@basbroek basbroek marked this pull request as draft July 24, 2025 18:13
@basbroek basbroek marked this pull request as ready for review July 25, 2025 08:51
@basbroek
Copy link
Contributor Author

basbroek commented Jul 25, 2025

@domoritz: working on this issue I noticed signal strings created by Vega-Lite all use escaped double quotes \" while single quotes would also be valid and might enhance readability of the compiled Vega specs. For instance

"signal": "\"a: \" + (isValid(datum[\"a\"]) ? datum[\"a\"] : \"\"+datum[\"a\"]) + \"; b: \" + (format(datum[\"b\"], \"\"))"

would become

"signal": "'a: ' + (isValid(datum['a']) ? datum['a'] : ''+datum['a']) + '; b: ' + (format(datum['b'], ''))"

Is there a reason for not using single quotes? Maybe I'm missing some edge-cases?

@domoritz
Copy link
Member

At least one reason would be that " are slightly easier to use with the default ' in the Vega-Lite code base. I suggested changing to " in #5361. We could do it but should merge any big pending prs first. I'm supportive of it if you would like to tackle the task (in a separate pull request of course).

@basbroek
Copy link
Contributor Author

@domoritz I'll be offline the next two weeks, but when I'm back, I would be happy to dig into the formatting of signal strings in a future pull request (after I have finished working on the backgrounds for text marks).

Copy link
Member

@domoritz domoritz left a comment

Choose a reason for hiding this comment

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

I made a copy in #9678 because this pull request has different images than the CI would output. Please use a PAT and try whether the CI builds the examples rather than manually building them.

expr: 'datum' | 'datum.datum' = 'datum',
): VgValueRef {
// tooltip fields with a format property are no strings
const fieldDefWithFormat = channelDef as {field: string; type: string; format: string};
Copy link
Member

Choose a reason for hiding this comment

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

how do you know that the type here is {field: string; type: string; format: string}?

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 still wrestling a bit with the elaborate type system in this repo 😅
I have replaced the cast with a type guard.

if (fieldDefWithFormat?.type === 'nominal' && !fieldDefWithFormat.format) {
const fieldString = `datum["${fieldDefWithFormat.field}"]`;
return {
signal: `isValid(${fieldString}) ? isArray(${fieldString}) ? join(${fieldString}, '\\n') : ${fieldString} : ""+${fieldString}`,
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to convert invalid values to strings? Can we reuse logic here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is in line with the way formatSignalRef handles nominal fields:

return {signal: `isValid(${field}) ? ${field} : ""+${field}`};

I think to render null, undefined, or NaN as strings in tooltips and area descriptions?

@basbroek
Copy link
Contributor Author

Please use a PAT and try whether the CI builds the examples rather than manually building them.

I'm sorry for that. I did add an action secret and tried to trigger the CI by including [SVG] in the last commit message, but that didn't work. Hence I built the examples locally to pass all CI tests. Won't happen again.

@domoritz
Copy link
Member

All good. Our setup for external forks is a bit wonky because of some restrictions GitHub actions had (have?). I often make an internal copy to get the build to work correctly.

@domoritz
Copy link
Member

Is it true that if there is a format, the tooltip is not a string? It's a string afterwards but I guess it's not likely to be a multi line string, right?

@basbroek
Copy link
Contributor Author

Is it true that if there is a format, the tooltip is not a string? It's a string afterwards but I guess it's not likely to be a multi line string, right?

The comment was unclear, I have removed it. The logic is:

  • Only tooltip fields with type nominal or ordinal have to be formatted as a vg-signal that checks for arrays with multi-line strings
  • If the spec doesn't contain an explicit type for a temporal or quantitative tooltip field, it gets the default type nominal earlier in compiling
  • If the tooltip field has type nominal but also a format specifier, it should fall thru so it can be formatted as a vg-signal that includes the format (e.g. "signal": \"count\": format(datum[\"count\"], \",\")

I hope this answers your question?

Copy link
Member

@domoritz domoritz left a comment

Choose a reason for hiding this comment

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

Looks good. Let's get the tests to pass and I can merge.

): VgValueRef {
// tooltip fields that are not nominal or have a format property are no strings
if (isFieldDef(channelDef) && 'type' in channelDef && channelDef.type === 'nominal' && !channelDef.format) {
if (isFieldDef(channelDef) && 'type' in channelDef && isDiscrete(channelDef.type) && !channelDef.format) {
Copy link
Member

Choose a reason for hiding this comment

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

isn't 'type' in channelDef always true?

@domoritz
Copy link
Member

I merged your changes into #9678.

@domoritz
Copy link
Member

We should make you a maintainer. Would you like to (https://github.com/vega/.github/blob/main/project-docs/MAINTAINERS.md)?

domoritz added a commit that referenced this pull request Sep 17, 2025
Internal version of #9647

---------

Co-authored-by: Bas Broekhuizen <[email protected]>
Co-authored-by: GitHub Actions Bot <[email protected]>
@domoritz
Copy link
Member

Thank you. Merged in #9678

@domoritz domoritz closed this Sep 17, 2025
@basbroek
Copy link
Contributor Author

@domoritz Thanks for reviewing and merging! If you have time, maybe you can have a look at the corresponding PR in vega-tooltip that is necessary to render the newlines in the tooltip.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support newline characters or max width for tooltips

2 participants