Skip to content

Commit 28669c4

Browse files
authored
tip title (#1539)
1 parent 9a8e214 commit 28669c4

File tree

6 files changed

+205
-167
lines changed

6 files changed

+205
-167
lines changed

src/mark.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,10 @@ export interface MarkOptions {
264264
pointerEvents?: string;
265265

266266
/**
267-
* The [title][1]; a channel specifying accessible, short textual descriptions
268-
* as strings (possibly with newlines).
267+
* The title; a channel specifying accessible, short textual descriptions as
268+
* strings (possibly with newlines). If the tip option is specified, the title
269+
* will be displayed with an interactive tooltip instead of using the SVG
270+
* [title element][1].
269271
*
270272
* [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
271273
*/

src/marks/text.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export class Text extends Mark {
119119
}
120120
}
121121

122-
function maybeTextOverflow(textOverflow) {
122+
export function maybeTextOverflow(textOverflow) {
123123
return textOverflow == null
124124
? null
125125
: keyword(textOverflow, "textOverflow", [
@@ -410,14 +410,14 @@ export function monospaceWidth(text, start = 0, end = text.length) {
410410
return sum;
411411
}
412412

413-
function splitter({monospace, lineWidth, textOverflow}) {
413+
export function splitter({monospace, lineWidth, textOverflow}) {
414414
if (textOverflow != null || lineWidth == Infinity) return (text) => text.split(/\r\n?|\n/g);
415415
const widthof = monospace ? monospaceWidth : defaultWidth;
416416
const maxWidth = lineWidth * 100;
417417
return (text) => lineWrap(text, maxWidth, widthof);
418418
}
419419

420-
function clipper({monospace, lineWidth, textOverflow}) {
420+
export function clipper({monospace, lineWidth, textOverflow}) {
421421
if (textOverflow == null || lineWidth == Infinity) return (text) => text;
422422
const widthof = monospace ? monospaceWidth : defaultWidth;
423423
const maxWidth = lineWidth * 100;

src/marks/tip.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {Mark} from "../mark.js";
77
import {maybeAnchor, maybeFrameAnchor, maybeTuple, number, string} from "../options.js";
88
import {applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform, impliedString} from "../style.js";
99
import {inferTickFormat} from "./axis.js";
10-
import {applyIndirectTextStyles, cut, defaultWidth, ellipsis, monospaceWidth} from "./text.js";
10+
import {applyIndirectTextStyles, defaultWidth, ellipsis, monospaceWidth} from "./text.js";
11+
import {cut, clipper, splitter, maybeTextOverflow} from "./text.js";
1112

1213
const defaults = {
1314
ariaLabel: "tip",
@@ -16,7 +17,7 @@ const defaults = {
1617
};
1718

1819
// These channels are not displayed in the tip; TODO allow customization.
19-
const ignoreChannels = new Set(["geometry", "title", "href", "src", "ariaLabel"]);
20+
const ignoreChannels = new Set(["geometry", "href", "src", "ariaLabel"]);
2021

2122
export class Tip extends Mark {
2223
constructor(data, options = {}) {
@@ -38,6 +39,7 @@ export class Tip extends Mark {
3839
lineWidth = 20,
3940
frameAnchor,
4041
textAnchor = "start",
42+
textOverflow,
4143
textPadding = 8,
4244
pointerSize = 12,
4345
pathFilter = "drop-shadow(0 3px 4px rgba(0,0,0,0.2))"
@@ -64,13 +66,16 @@ export class Tip extends Mark {
6466
this.pathFilter = string(pathFilter);
6567
this.lineHeight = +lineHeight;
6668
this.lineWidth = +lineWidth;
69+
this.textOverflow = maybeTextOverflow(textOverflow);
6770
this.monospace = !!monospace;
6871
this.fontFamily = string(fontFamily);
6972
this.fontSize = number(fontSize);
7073
this.fontStyle = string(fontStyle);
7174
this.fontVariant = string(fontVariant);
7275
this.fontWeight = string(fontWeight);
7376
for (const key in defaults) if (key in this.channels) this[key] = defaults[key]; // apply default even if channel
77+
this.splitLines = splitter(this);
78+
this.clipLine = clipper(this);
7479
}
7580
render(index, scales, channels, dimensions, context) {
7681
const mark = this;
@@ -106,6 +111,13 @@ export class Tip extends Mark {
106111
const formatFy = fy && inferTickFormat(fy);
107112

108113
function* format(sources, i) {
114+
if ("title" in sources) {
115+
const text = sources.title.value[i];
116+
for (const line of mark.splitLines(formatDefault(text))) {
117+
yield ["", mark.clipLine(line)];
118+
}
119+
return;
120+
}
109121
for (const key in sources) {
110122
if (key === "x1" && "x2" in sources) continue;
111123
if (key === "y1" && "y2" in sources) continue;

src/style.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export function applyTextGroup(selection, T) {
182182

183183
export function applyChannelStyles(
184184
selection,
185-
{target},
185+
{target, tip},
186186
{
187187
ariaLabel: AL,
188188
title: T,
@@ -203,12 +203,12 @@ export function applyChannelStyles(
203203
if (SW) applyAttr(selection, "stroke-width", (i) => SW[i]);
204204
if (O) applyAttr(selection, "opacity", (i) => O[i]);
205205
if (H) applyHref(selection, (i) => H[i], target);
206-
applyTitle(selection, T);
206+
if (!tip) applyTitle(selection, T);
207207
}
208208

209209
export function applyGroupedChannelStyles(
210210
selection,
211-
{target},
211+
{target, tip},
212212
{
213213
ariaLabel: AL,
214214
title: T,
@@ -229,21 +229,24 @@ export function applyGroupedChannelStyles(
229229
if (SW) applyAttr(selection, "stroke-width", ([i]) => SW[i]);
230230
if (O) applyAttr(selection, "opacity", ([i]) => O[i]);
231231
if (H) applyHref(selection, ([i]) => H[i], target);
232-
applyTitleGroup(selection, T);
232+
if (!tip) applyTitleGroup(selection, T);
233233
}
234234

235-
function groupAesthetics({
236-
ariaLabel: AL,
237-
title: T,
238-
fill: F,
239-
fillOpacity: FO,
240-
stroke: S,
241-
strokeOpacity: SO,
242-
strokeWidth: SW,
243-
opacity: O,
244-
href: H
245-
}) {
246-
return [AL, T, F, FO, S, SO, SW, O, H].filter((c) => c !== undefined);
235+
function groupAesthetics(
236+
{
237+
ariaLabel: AL,
238+
title: T,
239+
fill: F,
240+
fillOpacity: FO,
241+
stroke: S,
242+
strokeOpacity: SO,
243+
strokeWidth: SW,
244+
opacity: O,
245+
href: H
246+
},
247+
{tip}
248+
) {
249+
return [AL, tip ? undefined : T, F, FO, S, SO, SW, O, H].filter((c) => c !== undefined);
247250
}
248251

249252
export function groupZ(I, Z, z) {
@@ -256,9 +259,10 @@ export function groupZ(I, Z, z) {
256259
return G.values();
257260
}
258261

259-
export function* groupIndex(I, position, {z}, channels) {
262+
export function* groupIndex(I, position, mark, channels) {
263+
const {z} = mark;
260264
const {z: Z} = channels; // group channel
261-
const A = groupAesthetics(channels); // aesthetic channels
265+
const A = groupAesthetics(channels, mark); // aesthetic channels
262266
const C = [...position, ...A]; // all channels
263267

264268
// Group the current index by Z (if any).

0 commit comments

Comments
 (0)