1
- import { select , format as numberFormat } from "d3" ;
1
+ import { select , format as numberFormat , utcFormat } from "d3" ;
2
2
import { getSource } from "../channel.js" ;
3
3
import { create } from "../context.js" ;
4
4
import { defined } from "../defined.js" ;
@@ -7,7 +7,7 @@ import {anchorX, anchorY} from "../interactions/pointer.js";
7
7
import { Mark } from "../mark.js" ;
8
8
import { maybeAnchor , maybeFrameAnchor , maybeTuple , number , string } from "../options.js" ;
9
9
import { applyDirectStyles , applyFrameAnchor , applyIndirectStyles , applyTransform , impliedString } from "../style.js" ;
10
- import { identity , isIterable , isTextual , labelof , maybeValue } from "../options.js" ;
10
+ import { identity , isIterable , isTemporal , isTextual } from "../options.js" ;
11
11
import { inferTickFormat } from "./axis.js" ;
12
12
import { applyIndirectTextStyles , defaultWidth , ellipsis , monospaceWidth } from "./text.js" ;
13
13
import { cut , clipper , splitter , maybeTextOverflow } from "./text.js" ;
@@ -83,7 +83,7 @@ export class Tip extends Mark {
83
83
for ( const key in defaults ) if ( key in this . channels ) this [ key ] = defaults [ key ] ; // apply default even if channel
84
84
this . splitLines = splitter ( this ) ;
85
85
this . clipLine = clipper ( this ) ;
86
- this . format = maybeTipFormat ( this . channels , format ) ;
86
+ this . format = { ... format } ; // defensive copy, and promote nullish to empty
87
87
}
88
88
render ( index , scales , values , dimensions , context ) {
89
89
const mark = this ;
@@ -116,11 +116,24 @@ export class Tip extends Mark {
116
116
const widthof = monospace ? monospaceWidth : defaultWidth ;
117
117
const ee = widthof ( ellipsis ) ;
118
118
119
+ // Initialize shorthand formats. TODO Don’t mutate.
120
+ for ( const key in this . format ) {
121
+ const format = this . format [ key ] ;
122
+ if ( typeof format === "string" && key in sources ) {
123
+ this . format [ key ] = ( isTemporal ( sources [ key ] . value ) ? utcFormat : numberFormat ) ( format ) ;
124
+ }
125
+ }
126
+
127
+ // Initialize default formats for the facet scales. TODO Don’t mutate.
128
+ if ( index . fi != null ) {
129
+ const { fx, fy} = scales ;
130
+ if ( fx && this . format . fx === undefined ) this . format . fx = inferTickFormat ( fx , fx . domain ( ) ) ;
131
+ if ( fy && this . format . fy === undefined ) this . format . fy = inferTickFormat ( fy , fy . domain ( ) ) ;
132
+ }
133
+
119
134
// Determine the appropriate formatter.
120
135
const format =
121
- this . format !== undefined
122
- ? this . format // use the custom format, if any
123
- : "title" in sources // if there is a title channel
136
+ "title" in sources // if there is a title channel
124
137
? formatTitle // display the title as-is
125
138
: index . fi == null // if this mark is not faceted
126
139
? formatChannels // display name-value pairs for channels
@@ -173,8 +186,7 @@ export class Tip extends Mark {
173
186
// exact text metrics and translate the text as needed once we know the
174
187
// tip’s orientation (anchor).
175
188
function renderLine ( selection , { label, value, color, opacity} ) {
176
- label ??= "" ; // TODO fix earlier?
177
- value ??= "" ; // TODO fix earlier?
189
+ ( label ??= "" ) , ( value ??= "" ) ;
178
190
const swatch = color != null || opacity != null ;
179
191
let title ;
180
192
let w = lineWidth * 100 ;
@@ -318,60 +330,9 @@ function getSources({channels}) {
318
330
return sources ;
319
331
}
320
332
321
- // Note: mutates channels!
322
- function maybeTipFormat ( channels , format ) {
323
- if ( format === undefined ) return ;
324
- if ( typeof format === "function" ) {
325
- return function ( i , index , channels , scales , { data} ) {
326
- return format . call ( this , data [ i ] , i ) ;
327
- } ;
328
- }
329
- format = Array . from ( format , ( f ) => maybeTipFormatItem ( f , channels ) ) ;
330
- return function * ( i , index , channels , scales , values ) {
331
- for ( let { label, channel : key , format : formatValue } of format ) {
332
- if ( label === undefined ) label = formatLabel ( scales , channels , key ) ;
333
- if ( key === "fx" || key === "fy" ) {
334
- if ( formatValue === undefined ) formatValue = inferTickFormat ( scales [ key ] ) ; // TODO optimize
335
- yield {
336
- label,
337
- value : formatValue ( index [ key ] )
338
- } ;
339
- } else {
340
- if ( formatValue === undefined ) formatValue = formatDefault ;
341
- const channel = channels [ key ] ;
342
- const scale = channel . scale ;
343
- yield {
344
- label,
345
- value : formatValue ( channel . value [ i ] ) ,
346
- color : scale === "color" ? values [ key ] [ i ] : null ,
347
- opacity : scale === "opacity" ? values [ key ] [ i ] : null
348
- } ;
349
- }
350
- }
351
- } ;
352
- }
353
-
354
- // Note: mutates channels!
355
- function maybeTipFormatItem ( f , channels ) {
356
- if ( typeof f === "string" ) f = { channel : f } ; // shorthand channel name
357
- f = maybeValue ( f ) ; // shorthand function, array, etc.
358
- if ( typeof f . format === "string" ) f = { ...f , format : numberFormat ( f . format ) } ; // shorthand format; TODO dates
359
- if ( f . value !== undefined ) f = { ...f , channel : deriveChannel ( channels , f ) } ; // shorthand channel
360
- return f ;
361
- }
362
-
363
- let nextTipId = 0 ;
364
-
365
- // Note: mutates channels!
366
- function deriveChannel ( channels , f ) {
367
- const key = `--tip-${ ++ nextTipId } ` ; // TODO better anonymous channels
368
- const { value, label = labelof ( value ) ?? "" } = f ;
369
- channels [ key ] = { label, value, filter : null } ;
370
- return key ;
371
- }
372
-
373
333
function formatTitle ( i , index , { title} ) {
374
- return formatDefault ( title . value [ i ] ) ;
334
+ const format = this . format ?. title ;
335
+ return format === null ? [ ] : ( format ?? formatDefault ) ( title . value [ i ] ) ;
375
336
}
376
337
377
338
function * formatChannels ( i , index , channels , scales , values ) {
@@ -380,22 +341,28 @@ function* formatChannels(i, index, channels, scales, values) {
380
341
if ( key === "y1" && "y2" in channels ) continue ;
381
342
const channel = channels [ key ] ;
382
343
if ( key === "x2" && "x1" in channels ) {
344
+ const format = this . format ?. x ; // TODO x1, x2?
345
+ if ( format === null ) continue ;
383
346
yield {
384
347
label : formatPairLabel ( scales , channels , "x" ) ,
385
- value : formatPair ( channels . x1 , channel , i )
348
+ value : formatPair ( format ?? formatDefault , channels . x1 , channel , i )
386
349
} ;
387
350
} else if ( key === "y2" && "y1" in channels ) {
351
+ const format = this . format ?. y ; // TODO y1, y2?
352
+ if ( format === null ) continue ;
388
353
yield {
389
354
label : formatPairLabel ( scales , channels , "y" ) ,
390
- value : formatPair ( channels . y1 , channel , i )
355
+ value : formatPair ( format ?? formatDefault , channels . y1 , channel , i )
391
356
} ;
392
357
} else {
358
+ const format = this . format ?. [ key ] ;
359
+ if ( format === null ) continue ;
393
360
const value = channel . value [ i ] ;
394
361
const scale = channel . scale ;
395
- if ( ! defined ( value ) && scale == null ) return ;
362
+ if ( ! defined ( value ) && scale == null ) continue ;
396
363
yield {
397
364
label : formatLabel ( scales , channels , key ) ,
398
- value : formatDefault ( value ) ,
365
+ value : ( format ?? formatDefault ) ( value ) ,
399
366
color : scale === "color" ? values [ key ] [ i ] : null ,
400
367
opacity : scale === "opacity" ? values [ key ] [ i ] : null
401
368
} ;
@@ -404,25 +371,22 @@ function* formatChannels(i, index, channels, scales, values) {
404
371
}
405
372
406
373
function * formatFacetedChannels ( i , index , channels , scales , values ) {
407
- yield * formatChannels ( i , index , channels , scales , values ) ;
408
- if ( scales . fx ) {
409
- yield {
410
- label : formatLabel ( scales , channels , "fx" ) ,
411
- value : inferTickFormat ( scales . fx ) ( index . fx ) // TODO optimize
412
- } ;
413
- }
414
- if ( scales . fy ) {
374
+ yield * formatChannels . call ( this , i , index , channels , scales , values ) ;
375
+ for ( const key of [ "fx" , "fy" ] ) {
376
+ if ( ! scales [ key ] ) return ;
377
+ const format = this . format ?. [ key ] ;
378
+ if ( format === null ) continue ;
415
379
yield {
416
- label : formatLabel ( scales , channels , "fy" ) ,
417
- value : inferTickFormat ( scales . fy ) ( index . fy ) // TODO optimize
380
+ label : formatLabel ( scales , channels , key ) ,
381
+ value : ( format ?? formatDefault ) ( index [ key ] )
418
382
} ;
419
383
}
420
384
}
421
385
422
- function formatPair ( c1 , c2 , i ) {
386
+ function formatPair ( formatValue , c1 , c2 , i ) {
423
387
return c2 . hint ?. length // e.g., stackY’s y1 and y2
424
- ? `${ formatDefault ( c2 . value [ i ] - c1 . value [ i ] ) } `
425
- : `${ formatDefault ( c1 . value [ i ] ) } –${ formatDefault ( c2 . value [ i ] ) } ` ;
388
+ ? `${ formatValue ( c2 . value [ i ] - c1 . value [ i ] ) } `
389
+ : `${ formatValue ( c1 . value [ i ] ) } –${ formatValue ( c2 . value [ i ] ) } ` ;
426
390
}
427
391
428
392
function formatPairLabel ( scales , channels , key ) {
0 commit comments