Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/marks/tip.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Plot.plot({
```
:::

If no **title** channel is supplied, the tip mark displays all channel values. You can supply additional name-value pairs by registering extra channels using the **channels** mark option. In the scatterplot of Olympic athletes below, you can hover to see the *name* and *sport* of each athlete. This is helpful for noticing patterns — tall basketball players, giant weightlifters and judoka, diminutive gymnasts — and for seeing individuals.
If no **title** channel is supplied, the tip mark displays all channel values as name-value pairs, clipping long values with an ellipsis. Set **textOverflow** to *null* to disable clipping, or use another [text overflow](../marks/text.md) mode such as *ellipsis-middle*. You can supply additional name-value pairs by registering extra channels using the **channels** mark option. In the scatterplot of Olympic athletes below, you can hover to see the *name* and *sport* of each athlete. This is helpful for noticing patterns — tall basketball players, giant weightlifters and judoka, diminutive gymnasts — and for seeing individuals.

:::plot defer https://observablehq.com/@observablehq/plot-tips-additional-channels
```js
Expand Down Expand Up @@ -253,7 +253,7 @@ These [standard text options](./text.md#text-options) control the display of tex
- **textAnchor** - the [text anchor](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor) for horizontal position; *start*, *end*, or *middle*
- **lineHeight** - the line height in ems; defaults to 1
- **lineWidth** - the line width in ems, for wrapping; defaults to 20
- **textOverflow** - how to wrap or clip lines longer than the specified line width
- **textOverflow** - how to wrap or clip lines longer than the specified line width; defaults to *ellipsis* for name-value tips <VersionBadge pr="2322" />

## tip(*data*, *options*) {#tip}

Expand Down
2 changes: 1 addition & 1 deletion src/marks/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class Text extends Mark {

export function maybeTextOverflow(textOverflow) {
return textOverflow == null
? null
? textOverflow
: keyword(textOverflow, "textOverflow", [
"clip", // shorthand for clip-end
"ellipsis", // … ellipsis-end
Expand Down
13 changes: 6 additions & 7 deletions src/marks/tip.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export class Tip extends Mark {
const {x, y, fx, fy} = scales;
const {ownerSVGElement: svg, document} = context;
const {anchor, monospace, lineHeight, lineWidth} = this;
let {textOverflow} = this;
const {textPadding: r, pointerSize: m, pathFilter} = this;
const {marginTop, marginLeft} = dimensions;

Expand Down Expand Up @@ -126,6 +127,7 @@ export class Tip extends Mark {
} else {
sources = getSourceChannels.call(this, values.channels, scales);
format = formatChannels;
if (textOverflow === undefined) textOverflow = "ellipsis-end";
}

// Format the tip text, skipping any nulls.
Expand Down Expand Up @@ -191,13 +193,10 @@ export class Tip extends Mark {
title = value.trim();
value = "";
} else {
if (label || (!value && !swatch)) value = " " + value;
const [k] = cut(value, w - widthof(label), widthof, ee);
if (k >= 0) {
// value is truncated
title = value.trim();
value = value.slice(0, k).trimEnd() + ellipsis;
}
const space = label || (!value && !swatch) ? " " : "";
const clipped = clipper({monospace, lineWidth: lineWidth - widthof(label + space) / 100, textOverflow})(value);
if (clipped !== value) title = value.trim(); // show untruncated value in title
value = space + clipped;
}
const line = selection.append("tspan").attr("x", 0).attr("dy", `${lineHeight}em`).text("\u200b"); // zwsp for double-click
if (label) line.append("tspan").attr("font-weight", "bold").text(label);
Expand Down
31 changes: 31 additions & 0 deletions test/output/tipTextOverflowClipEnd.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions test/output/tipTextOverflowDefault.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions test/output/tipTextOverflowEllipsisEnd.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions test/output/tipTextOverflowEllipsisMiddle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions test/output/tipTextOverflowEllipsisStart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions test/output/tipTextOverflowNull.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions test/plots/tip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,48 @@ test(async function tipLongText() {
return Plot.tip([{x: "Long sentence that gets cropped after a certain length"}], {x: "x"}).plot();
});

test(async function tipTextOverflowNull() {
return Plot.tip([{x: "Long sentence that does not get clipped no matter how long it gets; it can be really long"}], {
x: "x",
textOverflow: null,
anchor: "top" // otherwise it would be bottom
}).plot();
});

test(async function tipTextOverflowClipEnd() {
return Plot.tip([{x: "Long sentence that gets clipped at the end"}], {
x: "x",
textOverflow: "clip" // shorthand for "clip-end"
}).plot();
});

test(async function tipTextOverflowDefault() {
return Plot.tip([{x: "Long sentence that gets an ellipsis at the end"}], {
x: "x"
}).plot();
});

test(async function tipTextOverflowEllipsisEnd() {
return Plot.tip([{x: "Long sentence that gets an ellipsis at the end"}], {
x: "x",
textOverflow: "ellipsis" // shorthand for "ellipsis-end"
}).plot();
});

test(async function tipTextOverflowEllipsisMiddle() {
return Plot.tip([{x: "Long sentence that gets an ellipsis in the middle"}], {
x: "x",
textOverflow: "ellipsis-middle"
}).plot();
});

test(async function tipTextOverflowEllipsisStart() {
return Plot.tip([{x: "Long sentence that gets an ellipsis at the start"}], {
x: "x",
textOverflow: "ellipsis-start"
}).plot();
});

test(async function tipNewLines() {
return Plot.plot({
height: 40,
Expand Down