Skip to content

Commit a285359

Browse files
committed
custom format option
1 parent a86ddbb commit a285359

File tree

4 files changed

+39
-3
lines changed

4 files changed

+39
-3
lines changed

src/marks/tip.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ export interface TipOptions extends MarkOptions, TextStyles {
6161
* the right of the anchor position.
6262
*/
6363
anchor?: FrameAnchor;
64+
65+
/**
66+
* A custom format function specifying what the tip shows. This function is
67+
* passed the datum d and zero-based index i for each tip. It may return
68+
* either a string, an object of name-value pairs, or an iterable of {name,
69+
* value, color, opacity} objects.
70+
*/
71+
format?: (d: any, i: number) => string | {[name: string]: string} | Iterable<TipItem>;
72+
}
73+
74+
/** A formatted line item to show in a tip. */
75+
export interface TipItem {
76+
name?: string;
77+
value?: string;
78+
color?: string;
79+
opacity?: number;
6480
}
6581

6682
/**

src/marks/tip.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import {defined} from "../defined.js";
55
import {formatDefault} from "../format.js";
66
import {anchorX, anchorY} from "../interactions/pointer.js";
77
import {Mark} from "../mark.js";
8-
import {maybeAnchor, maybeFrameAnchor, maybeTuple, number, string} from "../options.js";
8+
import {maybeAnchor, maybeFrameAnchor, maybeFunction, maybeTuple, number, string} from "../options.js";
99
import {applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform, impliedString} from "../style.js";
10-
import {identity, isIterable, isTextual} from "../options.js";
10+
import {identity, isIterable, isTextual, isObject} from "../options.js";
1111
import {inferTickFormat} from "./axis.js";
1212
import {applyIndirectTextStyles, defaultWidth, ellipsis, monospaceWidth} from "./text.js";
1313
import {cut, clipper, splitter, maybeTextOverflow} from "./text.js";
@@ -42,6 +42,7 @@ export class Tip extends Mark {
4242
lineHeight = 1,
4343
lineWidth = 20,
4444
frameAnchor,
45+
format,
4546
textAnchor = "start",
4647
textOverflow,
4748
textPadding = 8,
@@ -82,6 +83,7 @@ export class Tip extends Mark {
8283
for (const key in defaults) if (key in this.channels) this[key] = defaults[key]; // apply default even if channel
8384
this.splitLines = splitter(this);
8485
this.clipLine = clipper(this);
86+
this.format = maybeFunction(format);
8587
}
8688
render(index, scales, values, dimensions, context) {
8789
const mark = this;
@@ -116,7 +118,9 @@ export class Tip extends Mark {
116118

117119
// Determine the appropriate formatter.
118120
const format =
119-
"title" in sources // if there is a title channel
121+
this.format !== undefined
122+
? formatData(this.format, values.data) // use the custom format, if any
123+
: "title" in sources // if there is a title channel
120124
? formatTitle // display the title as-is
121125
: index.fi == null // if this mark is not faceted
122126
? formatChannels // display name-value pairs for channels
@@ -312,6 +316,14 @@ function getSources({channels}) {
312316
return sources;
313317
}
314318

319+
function formatData(format, data) {
320+
return function (i) {
321+
let result = format.call(this, data[i], i);
322+
if (isObject(result)) result = Object.entries(result).map(([name, value]) => ({name, value}));
323+
return result;
324+
};
325+
}
326+
315327
function formatTitle(i, {title}) {
316328
return formatDefault(title.value[i]);
317329
}

src/options.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ export function keyword(input, name, allowed) {
130130
return i;
131131
}
132132

133+
// Validates the specified optional function.
134+
export function maybeFunction(input, name) {
135+
if (input == null) return;
136+
if (typeof input !== "function") throw new Error(`invalid ${name}: ${input}`);
137+
return input;
138+
}
139+
133140
// Promotes the specified data to an array as needed.
134141
export function arrayify(data) {
135142
return data == null || data instanceof Array || data instanceof TypedArray ? data : Array.from(data);

src/plot.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ export function plot(options = {}) {
239239
// Compute value objects, applying scales and projection as needed.
240240
for (const [mark, state] of stateByMark) {
241241
state.values = mark.scale(state.channels, scales, context);
242+
state.values.data = state.data; // Expose the data, too.
242243
}
243244

244245
const {width, height} = dimensions;

0 commit comments

Comments
 (0)