Skip to content

Commit 7bfa337

Browse files
committed
tip preferredAnchor
1 parent 1b1da9f commit 7bfa337

File tree

7 files changed

+87
-8
lines changed

7 files changed

+87
-8
lines changed

docs/marks/tip.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ Plot.plot({
182182
```
183183
:::
184184

185-
If you don’t specify an **anchor**, the tip mark will choose one automatically. It will prefer *top-left* if the tip fits; otherwise it will switch sides to try to contain the tip within the plot’s frame. When dynamically rendering the tip mark, say with the [pointer interaction](../interactions/pointer.md), the tip will also attempt to use the anchor it chose previously, making the tip more stable as you move the pointer and improving readability. In some cases, it may not be possible to fit the tip within the plot’s frame; consider setting the plot’s **style** to `overflow: visible;` to prevent the tip from being truncated.
185+
If you don’t specify an explicit **anchor**, the tip mark will choose one automatically, using the **preferredAnchor** <VersionBadge pr="1872" /> if it fits. The preferred anchor defaults to *bottom*, except when using the **tip** option and the [pointerY pointing mode](../interactions/pointer.md), in which case it defaults to *left*. In some cases, it may not be possible to fit the tip within the plot’s frame; consider setting the plot’s **style** to `overflow: visible;` to prevent the tip from being truncated.
186186

187187
The tip mark is compatible with transforms that derive **x** and **y** dynamically from data, such as the [centroid transform](../transforms/centroid.md) which computes polygon centroids. Below, a map of the United States shows state names. We reduce the size of the tips by setting the **textPadding** option to 3 pixels instead of the default 8.
188188

src/marks/tip.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ export interface TipOptions extends MarkOptions, TextStyles {
6262
*/
6363
anchor?: FrameAnchor;
6464

65+
/**
66+
* If an explicit tip anchor is not specified, an anchor is chosen
67+
* automatically such that the tip fits within the plot’s frame; if the
68+
* preferred anchor fits, it is chosen.
69+
*/
70+
preferredAnchor?: FrameAnchor | null;
71+
6572
/**
6673
* How channel values are formatted for display. If a format is a string, it
6774
* is interpreted as a (UTC) time format for temporal channels, and otherwise

src/marks/tip.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class Tip extends Mark {
3333
y1,
3434
y2,
3535
anchor,
36+
preferredAnchor = "bottom",
3637
monospace,
3738
fontFamily = monospace ? "ui-monospace, monospace" : undefined,
3839
fontSize,
@@ -65,7 +66,7 @@ export class Tip extends Mark {
6566
defaults
6667
);
6768
this.anchor = maybeAnchor(anchor, "anchor");
68-
this.previousAnchor = this.anchor ?? "top-left";
69+
this.preferredAnchor = maybeAnchor(preferredAnchor, "preferredAnchor");
6970
this.frameAnchor = maybeFrameAnchor(frameAnchor);
7071
this.textAnchor = impliedString(textAnchor, "middle");
7172
this.textPadding = +textPadding;
@@ -208,16 +209,26 @@ export class Tip extends Mark {
208209
(w = Math.round(w)), (h = Math.round(h)); // crisp edges
209210
let a = anchor; // use the specified anchor, if any
210211
if (a === undefined) {
211-
a = mark.previousAnchor; // favor the previous anchor, if it fits
212212
const x = px(i) + ox;
213213
const y = py(i) + oy;
214214
const fitLeft = x + w + r * 2 < width;
215215
const fitRight = x - w - r * 2 > 0;
216216
const fitTop = y + h + m + r * 2 + 7 < height;
217217
const fitBottom = y - h - m - r * 2 > 0;
218-
const ax = (/-left$/.test(a) ? fitLeft || !fitRight : fitLeft && !fitRight) ? "left" : "right";
219-
const ay = (/^top-/.test(a) ? fitTop || !fitBottom : fitTop && !fitBottom) ? "top" : "bottom";
220-
a = mark.previousAnchor = `${ay}-${ax}`;
218+
a =
219+
fitLeft && fitRight
220+
? fitTop && fitBottom
221+
? mark.preferredAnchor
222+
: fitBottom
223+
? "bottom"
224+
: "top"
225+
: fitTop && fitBottom
226+
? fitLeft
227+
? "left"
228+
: "right"
229+
: (fitLeft || fitRight) && (fitTop || fitBottom)
230+
? `${fitBottom ? "bottom" : "top"}-${fitLeft ? "left" : "right"}`
231+
: mark.preferredAnchor;
221232
}
222233
const path = this.firstChild; // note: assumes exactly two children!
223234
const text = this.lastChild; // note: assumes exactly two children!

src/plot.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,10 +527,11 @@ function inferTips(marks) {
527527
if (tipOptions) {
528528
if (tipOptions === true) tipOptions = {};
529529
else if (typeof tipOptions === "string") tipOptions = {pointer: tipOptions};
530-
let {pointer: p} = tipOptions;
530+
let {pointer: p, preferredAnchor: a} = tipOptions;
531531
p = /^x$/i.test(p) ? pointerX : /^y$/i.test(p) ? pointerY : pointer; // TODO validate?
532532
tipOptions = p(derive(mark, tipOptions));
533533
tipOptions.title = null; // prevent implicit title for primitive data
534+
if (a === undefined) tipOptions.preferredAnchor = p === pointerY ? "left" : "bottom";
534535
const t = tip(mark.data, tipOptions);
535536
t.facet = mark.facet; // inherit facet settings
536537
t.facetAnchor = mark.facetAnchor; // inherit facet settings

test/output/tipLineX.svg

Lines changed: 55 additions & 0 deletions
Loading
File renamed without changes.

test/plots/tip.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,12 @@ export async function tipHexbinExplicit() {
171171
});
172172
}
173173

174-
export async function tipLine() {
174+
export async function tipLineX() {
175+
const aapl = await d3.csv<any>("data/aapl.csv", d3.autoType);
176+
return Plot.lineX(aapl, {y: "Date", x: "Close", tip: true}).plot();
177+
}
178+
179+
export async function tipLineY() {
175180
const aapl = await d3.csv<any>("data/aapl.csv", d3.autoType);
176181
return Plot.lineY(aapl, {x: "Date", y: "Close", tip: true}).plot();
177182
}

0 commit comments

Comments
 (0)