Skip to content

Commit 0dcec9f

Browse files
authored
custom tip format (#1823)
* custom tip format * format order; materialize defaults; fix facets * revert data values, for now * fix default tip pointer * fix paired channels * fix paired channel order * fix crash with inferred tick format * pReTTier * oops, time zones!
1 parent e98ccb0 commit 0dcec9f

36 files changed

+1395
-79
lines changed

src/channel.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export type ChannelValue =
145145
* object to override the scale that would normally be associated with the
146146
* channel.
147147
*/
148-
export type ChannelValueSpec = ChannelValue | {value: ChannelValue; scale?: Channel["scale"]}; // TODO label
148+
export type ChannelValueSpec = ChannelValue | {value: ChannelValue; label?: string; scale?: Channel["scale"]};
149149

150150
/**
151151
* In some contexts, when specifying a mark channel’s value, you can provide a

src/channel.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import {registry} from "./scales/index.js";
55
import {isSymbol, maybeSymbol} from "./symbol.js";
66
import {maybeReduce} from "./transforms/group.js";
77

8-
export function createChannel(data, {scale, type, value, filter, hint}, name) {
8+
export function createChannel(data, {scale, type, value, filter, hint, label = labelof(value)}, name) {
99
if (hint === undefined && typeof value?.transform === "function") hint = value.hint;
1010
return inferChannelScale(name, {
1111
scale,
1212
type,
1313
value: valueof(data, value),
14-
label: labelof(value),
14+
label,
1515
filter,
1616
hint
1717
});

src/mark.d.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {Channel, ChannelDomainSort, ChannelValue, ChannelValues, ChannelValueSpec} from "./channel.js";
22
import type {Context} from "./context.js";
33
import type {Dimensions} from "./dimensions.js";
4+
import type {TipOptions} from "./marks/tip.js";
45
import type {plot} from "./plot.js";
56
import type {ScaleFunctions} from "./scales.js";
67
import type {InitializerFunction, SortOrder, TransformFunction} from "./transforms/basic.js";
@@ -23,6 +24,9 @@ export type FrameAnchor =
2324
| "bottom-left"
2425
| "left";
2526

27+
/** The pointer mode for the tip; corresponds to pointerX, pointerY, and pointer. */
28+
export type TipPointer = "x" | "y" | "xy";
29+
2630
/**
2731
* A mark’s data; one of:
2832
*
@@ -275,8 +279,8 @@ export interface MarkOptions {
275279
*/
276280
title?: ChannelValue;
277281

278-
/** Whether to generate a tooltip for this mark. */
279-
tip?: boolean | "x" | "y" | "xy";
282+
/** Whether to generate a tooltip for this mark, and any tip options. */
283+
tip?: boolean | TipPointer | (TipOptions & {pointer?: TipPointer});
280284

281285
/**
282286
* How to clip the mark; one of:

src/mark.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {channelDomain, createChannels, valueObject} from "./channel.js";
22
import {defined} from "./defined.js";
33
import {maybeFacetAnchor} from "./facet.js";
4-
import {maybeKeyword, maybeNamed, maybeValue} from "./options.js";
5-
import {arrayify, isDomainSort, isOptions, keyword, range, singleton} from "./options.js";
4+
import {maybeNamed, maybeValue} from "./options.js";
5+
import {arrayify, isDomainSort, isObject, isOptions, keyword, range, singleton} from "./options.js";
66
import {project} from "./projection.js";
77
import {maybeClip, styles} from "./style.js";
88
import {basic, initializer} from "./transforms/basic.js";
@@ -150,17 +150,27 @@ export function composeRender(r1, r2) {
150150
function maybeChannels(channels) {
151151
return Object.fromEntries(
152152
Object.entries(maybeNamed(channels)).map(([name, channel]) => {
153-
channel = maybeValue(channel);
153+
channel = typeof channel === "string" ? {value: channel, label: name} : maybeValue(channel); // for shorthand extra channels, use name as label
154154
if (channel.filter === undefined && channel.scale == null) channel = {...channel, filter: null};
155155
return [name, channel];
156156
})
157157
);
158158
}
159159

160160
function maybeTip(tip) {
161-
return tip === true ? "xy" : tip === false ? null : maybeKeyword(tip, "tip", ["x", "y", "xy"]);
161+
return tip === true
162+
? "xy"
163+
: tip === false || tip == null
164+
? null
165+
: typeof tip === "string"
166+
? keyword(tip, "tip", ["x", "y", "xy"])
167+
: tip; // tip options object
162168
}
163169

164-
export function withTip(options, tip) {
165-
return options?.tip === true ? {...options, tip} : options;
170+
export function withTip(options, pointer) {
171+
return options?.tip === true
172+
? {...options, tip: pointer}
173+
: isObject(options?.tip) && options.tip.pointer === undefined
174+
? {...options, tip: {...options.tip, pointer}}
175+
: options;
166176
}

src/marks/tip.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {ChannelValueSpec} from "../channel.js";
1+
import type {ChannelName, ChannelValueSpec} from "../channel.js";
22
import type {Data, FrameAnchor, MarkOptions, RenderableMark} from "../mark.js";
33
import type {TextStyles} from "./text.js";
44

@@ -61,6 +61,13 @@ export interface TipOptions extends MarkOptions, TextStyles {
6161
* the right of the anchor position.
6262
*/
6363
anchor?: FrameAnchor;
64+
65+
/**
66+
* How channel values are formatted for display. If a format is a string, it
67+
* is interpreted as a (UTC) time format for temporal channels, and otherwise
68+
* a number format.
69+
*/
70+
format?: {[name in ChannelName]?: boolean | string | ((d: any, i: number) => string)};
6471
}
6572

6673
/**

0 commit comments

Comments
 (0)