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

feat: allow custom tooltip formatting #8883

Merged
merged 19 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions build/vega-lite-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7765,6 +7765,10 @@
"$ref": "#/definitions/TitleConfig",
"description": "Title configuration, which determines default properties for all [titles](https://vega.github.io/vega-lite/docs/title.html). For a full list of title configuration options, please see the [corresponding section of the title documentation](https://vega.github.io/vega-lite/docs/title.html#config)."
},
"tooltipFormat": {
"$ref": "#/definitions/FormatConfig",
"description": "Define [custom format configuration](https://vega.github.io/vega-lite/docs/config.html#format) for tooltips. If unspecified, default format config will be applied."
},
"trail": {
"$ref": "#/definitions/LineConfig",
"description": "Trail-Specific Config"
Expand Down Expand Up @@ -10851,6 +10855,36 @@
"number"
]
},
"FormatConfig": {
"additionalProperties": false,
"properties": {
"normalizedNumberFormat": {
"description": "If normalizedNumberFormatType is not specified, D3 number format for axis labels, text marks, and tooltips of normalized stacked fields (fields with `stack: \"normalize\"`). For example `\"s\"` for SI units. Use [D3's number format pattern](https://github.com/d3/d3-format#locale_format).\n\nIf `config.normalizedNumberFormatType` is specified and `config.customFormatTypes` is `true`, this value will be passed as `format` alongside `datum.value` to the `config.numberFormatType` function. __Default value:__ `%`",
"type": "string"
},
"normalizedNumberFormatType": {
"description": "[Custom format type](https://vega.github.io/vega-lite/docs/config.html#custom-format-type) for `config.normalizedNumberFormat`.\n\n__Default value:__ `undefined` -- This is equilvalent to call D3-format, which is exposed as [`format` in Vega-Expression](https://vega.github.io/vega/docs/expressions/#format). __Note:__ You must also set `customFormatTypes` to `true` to use this feature.",
"type": "string"
},
"numberFormat": {
"description": "If numberFormatType is not specified, D3 number format for guide labels, text marks, and tooltips of non-normalized fields (fields *without* `stack: \"normalize\"`). For example `\"s\"` for SI units. Use [D3's number format pattern](https://github.com/d3/d3-format#locale_format).\n\nIf `config.numberFormatType` is specified and `config.customFormatTypes` is `true`, this value will be passed as `format` alongside `datum.value` to the `config.numberFormatType` function.",
"type": "string"
},
"numberFormatType": {
"description": "[Custom format type](https://vega.github.io/vega-lite/docs/config.html#custom-format-type) for `config.numberFormat`.\n\n__Default value:__ `undefined` -- This is equilvalent to call D3-format, which is exposed as [`format` in Vega-Expression](https://vega.github.io/vega/docs/expressions/#format). __Note:__ You must also set `customFormatTypes` to `true` to use this feature.",
"type": "string"
},
"timeFormat": {
"description": "Default time format for raw time values (without time units) in text marks, legend labels and header labels.\n\n__Default value:__ `\"%b %d, %Y\"` __Note:__ Axes automatically determine the format for each label automatically so this config does not affect axes.",
"type": "string"
},
"timeFormatType": {
"description": "[Custom format type](https://vega.github.io/vega-lite/docs/config.html#custom-format-type) for `config.timeFormat`.\n\n__Default value:__ `undefined` -- This is equilvalent to call D3-time-format, which is exposed as [`timeFormat` in Vega-Expression](https://vega.github.io/vega/docs/expressions/#timeFormat). __Note:__ You must also set `customFormatTypes` to `true` and there must *not* be a `timeUnit` defined to use this feature.",
"type": "string"
}
},
"type": "object"
},
"Generator": {
"anyOf": [
{
Expand Down
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 examples/compiled/config_numberFormatType_tooltip.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
126 changes: 126 additions & 0 deletions examples/compiled/config_numberFormatType_tooltip.vg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "Testing global number formatting config",
"background": "white",
"padding": 5,
"width": 150,
"height": 150,
"style": "cell",
"data": [
{
"name": "source_0",
"url": "data/cars.json",
"format": {"type": "json", "parse": {"Year": "date"}},
"transform": [
{
"type": "aggregate",
"groupby": ["Year"],
"ops": ["average"],
"fields": ["Miles_per_Gallon"],
"as": ["average_Miles_per_Gallon"]
},
{
"type": "filter",
"expr": "(isDate(datum[\"Year\"]) || (isValid(datum[\"Year\"]) && isFinite(+datum[\"Year\"]))) && isValid(datum[\"average_Miles_per_Gallon\"]) && isFinite(+datum[\"average_Miles_per_Gallon\"])"
}
]
}
],
"marks": [
{
"name": "marks",
"type": "rect",
"style": ["bar"],
"from": {"data": "source_0"},
"encode": {
"update": {
"tooltip": {
"signal": "{\"Year\": timeFormat(datum[\"Year\"], '%b %d, %Y'), \"Average of Miles_per_Gallon\": format(datum[\"average_Miles_per_Gallon\"], \".8f\")}"
},
"fill": {"value": "#4c78a8"},
"ariaRoleDescription": {"value": "bar"},
"description": {
"signal": "\"Year: \" + (timeFormat(datum[\"Year\"], '%b %d, %Y')) + \"; Average of Miles_per_Gallon: \" + (format(datum[\"average_Miles_per_Gallon\"], \".8f\"))"
},
"xc": {"scale": "x", "field": "Year"},
"width": {"value": 5},
"y": {"scale": "y", "field": "average_Miles_per_Gallon"},
"y2": {"scale": "y", "value": 0}
}
}
}
],
"scales": [
{
"name": "x",
"type": "time",
"domain": {"data": "source_0", "field": "Year"},
"range": [0, {"signal": "width"}],
"padding": 5
},
{
"name": "y",
"type": "linear",
"domain": {"data": "source_0", "field": "average_Miles_per_Gallon"},
"range": [{"signal": "height"}, 0],
"nice": true,
"zero": true
}
],
"axes": [
{
"scale": "x",
"orient": "bottom",
"gridScale": "y",
"grid": true,
"tickCount": {"signal": "ceil(width/40)"},
"domain": false,
"labels": false,
"aria": false,
"maxExtent": 0,
"minExtent": 0,
"ticks": false,
"zindex": 0
},
{
"scale": "y",
"orient": "left",
"gridScale": "x",
"grid": true,
"tickCount": {"signal": "ceil(height/40)"},
"tickMinStep": 1,
"domain": false,
"labels": false,
"aria": false,
"maxExtent": 0,
"minExtent": 0,
"ticks": false,
"zindex": 0
},
{
"scale": "x",
"orient": "bottom",
"grid": false,
"title": "Year",
"labelFlush": true,
"labelOverlap": true,
"tickCount": {"signal": "ceil(width/40)"},
"zindex": 0
},
{
"scale": "y",
"orient": "left",
"grid": false,
"title": "Average of Miles_per_Gallon",
"format": "d",
"labelOverlap": true,
"tickCount": {"signal": "ceil(height/40)"},
"tickMinStep": 1,
"zindex": 0
}
],
"config": {
"tooltipFormat": {"numberFormat": ".8f"},
"customFormatTypes": true
}
}
19 changes: 19 additions & 0 deletions examples/specs/config_numberFormatType_tooltip.vl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"description": "Testing global number formatting config",
"width": 150,
"height": 150,
"data": {"url": "data/cars.json"},
"mark": {"type": "bar", "tooltip": true},
"encoding": {
"x": {"field": "Year", "type": "temporal"},
"y": {"field": "Miles_per_Gallon", "type": "quantitative", "aggregate": "average"}
},
"config": {
"tooltipFormat": {
"numberFormat": ".8f"
},
"numberFormat": "d",
"customFormatTypes": true
}
}
14 changes: 10 additions & 4 deletions site/docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ The rest of this page outlines different types of config properties:

A Vega-Lite `config` object can have the following top-level properties:

{% include table.html props="autosize,background,countTitle,fieldTitle,font,lineBreak,padding" source="Config" %}
{% include table.html props="autosize,background,countTitle,fieldTitle,font,lineBreak,padding,tooltipFormat" source="Config" %}

{:#format}

## Format Configuration

These config properties define the default number and time formats for text marks as well as axes, headers, and legends:
These config properties define the default number and time formats for text marks as well as axes, headers, tooltip, and legends:

{% include table.html props="numberFormat,numberFormatType,normalizedNumberFormat,normalizedNumberFormatType,timeFormat,timeFormatType,customFormatTypes" source="Config" %}

Expand All @@ -69,7 +69,7 @@ vega.expressionFunction('customFormatA', function(datum, params) {
});
```

2. Setting the `customFormatTypes` config to `true`.
(2) Setting the `customFormatTypes` config to `true`.

```js
{
Expand All @@ -78,7 +78,7 @@ vega.expressionFunction('customFormatA', function(datum, params) {
}
```

3. You can then use this custom format function with `format` and `formatType` properties in text encodings and guides (axis/legend/header).
(3) You can then use this custom format function with `format` and `formatType` properties in text encodings and guides (axis/legend/header).

```js
{
Expand All @@ -87,6 +87,12 @@ vega.expressionFunction('customFormatA', function(datum, params) {
}
```

### Customize Formatter for Tooltips only

Since tooltips have more screen estate and less chance of collisions, sometimes it is desirable to have a truncated format in a visualization, with a longer format in the tooltip. For example, in the visualization below, we want the y-axis to have the format `d` so it does not have a decimal point, so as not to have incredibly long labels, but on the tooltip it has the longer `.8f`. To achieve this specificity, one can add a `tooltipFormat` prop to their config that conforms to the [FormatConfig](#format) type.

<span class="vl-example" data-name="config_numberFormatType_tooltip"></span>
lsh marked this conversation as resolved.
Show resolved Hide resolved

{:#axis-config}

## Guide Configurations
Expand Down
9 changes: 5 additions & 4 deletions src/compile/mark/encode/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export function tooltipData(
config: Config,
{reactiveGeom}: {reactiveGeom?: boolean} = {}
) {
const formatConfig = {...config, ...config.tooltipFormat};
const toSkip = {};
const expr = reactiveGeom ? 'datum.datum' : 'datum';
const tuples: {channel: Channel; key: string; value: string}[] = [];
Expand All @@ -86,7 +87,7 @@ export function tooltipData(
type: (encoding[mainChannel] as TypedFieldDef<any>).type // for secondary field def, copy type from main channel
};

const title = fieldDef.title || defaultTitle(fieldDef, config);
const title = fieldDef.title || defaultTitle(fieldDef, formatConfig);
const key = array(title).join(', ');

let value: string;
Expand All @@ -99,7 +100,7 @@ export function tooltipData(
const startField = vgField(fieldDef, {expr});
const endField = vgField(fieldDef2, {expr});
const {format, formatType} = getFormatMixins(fieldDef);
value = binFormatExpression(startField, endField, format, formatType, config);
value = binFormatExpression(startField, endField, format, formatType, formatConfig);
toSkip[channel2] = true;
}
}
Expand All @@ -116,12 +117,12 @@ export function tooltipData(
format,
formatType,
expr,
config,
config: formatConfig,
normalizeStack: true
}).signal;
}

value ??= textRef(fieldDef, config, expr).signal;
value ??= textRef(fieldDef, formatConfig, expr).signal;

tuples.push({channel, key, value});
}
Expand Down
Loading