Skip to content

Commit

Permalink
tip title format (#2074)
Browse files Browse the repository at this point in the history
* tip title format

* channel, not title
  • Loading branch information
mbostock authored Jun 8, 2024
1 parent fb701a0 commit 7d48d4a
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 11 deletions.
2 changes: 1 addition & 1 deletion docs/marks/tip.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Plot.rectY(olympians, Plot.binX({y: "sum"}, {x: "weight", y: (d) => d.sex === "m
```
:::

The order and formatting of channels in the tip can be customized with the **format** option <VersionBadge version="0.6.11" pr="1823" />, which accepts a key-value object mapping channel names to formats. Each [format](../features/formats.md) can be a string (for number or time formats), a function that receives the value as input and returns a string, true to use the default format, and null or false to suppress. The order of channels in the tip follows their order in the format object followed by any additional channels.
The order and formatting of channels in the tip can be customized with the **format** option <VersionBadge version="0.6.11" pr="1823" />, which accepts a key-value object mapping channel names to formats. Each [format](../features/formats.md) can be a string (for number or time formats), a function that receives the value as input and returns a string, true to use the default format, and null or false to suppress. The order of channels in the tip follows their order in the format object followed by any additional channels. When using the **title** channel, the **format** option may be specified as a string or a function; the given format will then apply to the **title** channel. <VersionBadge pr="2074" />

A channel’s label can be specified alongside its value as a {value, label} object; if a channel label is not specified, the associated scale’s label is used, if any; if there is no associated scale, or if the scale has no label, the channel name is used instead.

Expand Down
14 changes: 13 additions & 1 deletion src/marks/tip.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface TipOptions extends MarkOptions, TextStyles {
* is interpreted as a (UTC) time format for temporal channels, and otherwise
* a number format.
*/
format?: {[name in ChannelName]?: boolean | string | ((d: any, i: number) => string)};
format?: {[name in ChannelName]?: null | boolean | TipFormat} | TipFormat;

/** The image filter for the tip’s box; defaults to a drop shadow. */
pathFilter?: string;
Expand All @@ -86,6 +86,18 @@ export interface TipOptions extends MarkOptions, TextStyles {
textPadding?: number;
}

/**
* How to format channel values; one of:
*
* - a [d3-format][1] string for numeric scales
* - a [d3-time-format][2] string for temporal scales
* - a function passed a channel *value* and *index*, returning a string
*
* [1]: https://d3js.org/d3-time
* [2]: https://d3js.org/d3-time-format
*/
export type TipFormat = string | ((d: any, i: number) => string);

/**
* Returns a new tip mark for the given *data* and *options*.
*
Expand Down
10 changes: 5 additions & 5 deletions src/marks/tip.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class Tip extends Mark {
for (const key in defaults) if (key in this.channels) this[key] = defaults[key]; // apply default even if channel
this.splitLines = splitter(this);
this.clipLine = clipper(this);
this.format = {...format}; // defensive copy before mutate; also promote nullish to empty
this.format = typeof format === "string" || typeof format === "function" ? {title: format} : {...format}; // defensive copy before mutate; also promote nullish to empty
}
render(index, scales, values, dimensions, context) {
const mark = this;
Expand Down Expand Up @@ -120,10 +120,10 @@ export class Tip extends Mark {
// channels as name-value pairs.
let sources, format;
if ("title" in values) {
sources = values.channels;
sources = getSourceChannels.call(this, {title: values.channels.title}, scales);
format = formatTitle;
} else {
sources = getSourceChannels.call(this, values, scales);
sources = getSourceChannels.call(this, values.channels, scales);
format = formatChannels;
}

Expand Down Expand Up @@ -319,7 +319,7 @@ function getPath(anchor, m, r, width, height) {
}

// Note: mutates this.format!
function getSourceChannels({channels}, scales) {
function getSourceChannels(channels, scales) {
const sources = {};

// Promote x and y shorthand for paired channels (in order).
Expand Down Expand Up @@ -384,7 +384,7 @@ function maybeExpandPairedFormat(format, channels, key) {
}

function formatTitle(i, index, {title}) {
return formatDefault(title.value[i], i);
return this.format.title(title.value[i], i);
}

function* formatChannels(i, index, channels, scales, values) {
Expand Down
2 changes: 1 addition & 1 deletion src/scales.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ export interface ScaleOptions extends ScaleDefaults {
* [1]: https://d3js.org/d3-time
* [2]: https://d3js.org/d3-time-format
*/
tickFormat?: string | ((t: any, i: number) => any) | null;
tickFormat?: string | ((d: any, i: number) => any) | null;

/**
* The rotation angle of axis tick labels in degrees clocksize; defaults to 0.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions test/output/tipFormatTitleFormatFunction.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions test/output/tipFormatTitleFormatShorthand.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions test/plots/tip-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,16 @@ export async function tipFormatTitleExplicit() {
return tip({length: 1}, {title: [new Date("2010-01-01")]});
}

export async function tipFormatTitleIgnoreFormat() {
return tip({length: 1}, {title: [0], format: {title: ".2f"}});
export async function tipFormatTitleFormat() {
return tip({length: 1}, {title: [0.009], format: {title: ".2f"}});
}

export async function tipFormatTitleFormatFunction() {
return tip({length: 1}, {title: [0.019], format: (d) => d.toFixed(2)});
}

export async function tipFormatTitleFormatShorthand() {
return tip({length: 1}, {title: [0.029], format: ".2f"});
}

export async function tipFormatTitlePrimitive() {
Expand Down

0 comments on commit 7d48d4a

Please sign in to comment.