Skip to content

Tooltips! #1304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b3f968b
tooltip
mbostock Mar 1, 2023
3eff24c
show all channels
mbostock May 2, 2023
3383a19
extra channels!
mbostock May 2, 2023
753f50f
svg tooltip
mbostock May 2, 2023
56cfcaa
filter: drop-shadow, overflow: visible
mbostock May 2, 2023
b183bda
fix date label
mbostock May 2, 2023
4b9fc1d
bold labels
mbostock May 2, 2023
72b83e1
tooltip dodge test
mbostock May 3, 2023
7812b1b
dodge fixes
mbostock May 3, 2023
c5ac532
zwsp
mbostock May 3, 2023
b2e790c
click to stick!
mbostock May 3, 2023
0440877
four corners
mbostock May 3, 2023
ba0c986
multi-line test
mbostock May 3, 2023
4b65637
preferred corner
mbostock May 4, 2023
003c162
tooltip text styles
mbostock May 4, 2023
bab44f1
listen to the window
mbostock May 4, 2023
631f4d1
remove default overflow: visible
mbostock May 4, 2023
7ded64c
declare maxRadius, corner
mbostock May 4, 2023
634e51e
truncate long values
mbostock May 4, 2023
742dc5c
one-dimensional bias
mbostock May 4, 2023
f161aa8
fixed corner option
mbostock May 4, 2023
74fa81a
preserve corner when possible
mbostock May 4, 2023
426b8ef
test tooltips on bin, stack
mbostock May 4, 2023
d08aaa2
avoid conflicting listeners
mbostock May 5, 2023
d2eb0b1
tidy event listeners
mbostock May 5, 2023
32e5d22
don’t listen to window
mbostock May 5, 2023
44a1291
tooltip for extents
mbostock May 5, 2023
9b9e868
show y2-y1 for stack
mbostock May 5, 2023
f655983
s/corner/anchor/
mbostock May 5, 2023
f5dc9dc
fix anchor oscillation
mbostock May 5, 2023
6bccc91
append tooltip asynchronously
mbostock May 5, 2023
2261a39
fix lineHeight
mbostock May 5, 2023
efad9d7
simplify text layout
mbostock May 6, 2023
36d6a37
mobile tooltip on tap (#1526)
Fil May 6, 2023
97b93c6
simpler pointer-events
mbostock May 7, 2023
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
2 changes: 1 addition & 1 deletion src/channel.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export type ChannelName =
* An object literal of channel definitions. This is also used to represent
* materialized channel states after mark initialization.
*/
export type Channels = {[key in ChannelName]?: Channel};
export type Channels = Record<string, Channel>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve changed this here to allow specifying custom channels for tooltips, but we might want to make this change in more places… and we might even want to change the ChannelName type to include string so that it’s always allowed, but use the never hack to still let VS Code autocomplete known channel names. Although, that makes it less likely we will detect typos.


/**
* A channel definition. This is also used to represent the materialized channel
Expand Down
2 changes: 1 addition & 1 deletion src/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {registry} from "./scales/index.js";
import {isSymbol, maybeSymbol} from "./symbol.js";
import {maybeReduce} from "./transforms/group.js";

// TODO Type coercion?
export function createChannel(data, {scale, type, value, filter, hint}, name) {
if (hint === undefined && typeof value?.transform === "function") hint = value.hint;
return inferChannelScale(name, {
scale,
type,
Expand Down
3 changes: 3 additions & 0 deletions src/context.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export interface Context {
*/
document: Document;

/** The current owner SVG element. */
ownerSVGElement: SVGSVGElement;

/** The Plot’s (typically generated) class name, for custom styles. */
className: string;

Expand Down
7 changes: 6 additions & 1 deletion src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import {createProjection} from "./projection.js";

export function createContext(options = {}, dimensions, className) {
const {document = typeof window !== "undefined" ? window.document : undefined} = options;
return {document, className, projection: createProjection(options, dimensions)};
return {
document,
ownerSVGElement: creator("svg").call(document.documentElement),
className,
projection: createProjection(options, dimensions)
};
}

export function create(name, {document}) {
Expand Down
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export * from "./marks/rect.js";
export * from "./marks/rule.js";
export * from "./marks/text.js";
export * from "./marks/tick.js";
export * from "./marks/tooltip.js";
export * from "./marks/tree.js";
export * from "./marks/vector.js";
export * from "./options.js";
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export {Rect, rect, rectX, rectY} from "./marks/rect.js";
export {RuleX, RuleY, ruleX, ruleY} from "./marks/rule.js";
export {Text, text, textX, textY} from "./marks/text.js";
export {TickX, TickY, tickX, tickY} from "./marks/tick.js";
export {Tooltip, tooltip} from "./marks/tooltip.js";
export {tree, cluster} from "./marks/tree.js";
export {Vector, vector, vectorX, vectorY, spike} from "./marks/vector.js";
export {valueof, column, identity, indexOf} from "./options.js";
Expand Down
4 changes: 3 additions & 1 deletion src/marks/axis.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ function inferTextChannel(scale, ticks, tickFormat) {
// D3’s ordinal scales simply use toString by default, but if the ordinal scale
// domain (or ticks) are numbers or dates (say because we’re applying a time
// interval to the ordinal scale), we want Plot’s default formatter.
function inferTickFormat(scale, ticks, tickFormat) {
export function inferTickFormat(scale, ticks, tickFormat) {
return scale.tickFormat
? scale.tickFormat(isIterable(ticks) ? null : ticks, tickFormat)
: tickFormat === undefined
Expand Down Expand Up @@ -635,6 +635,8 @@ function inferScaleOrder(scale) {
// inferred from an associated channel, adds an orientation-appropriate arrow.
function inferAxisLabel(key, scale, labelAnchor) {
const label = scale.label;
// Ignore the implicit label for temporal scales if it’s simply “date”.
if (label?.inferred && isTemporalScale(scale) && /^(date|time|year)$/i.test(label)) return;
if (scale.bandwidth || !label?.inferred) return label;
const order = inferScaleOrder(scale);
return order
Expand Down
2 changes: 1 addition & 1 deletion src/marks/dot.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class Dot extends Mark {
const {x: X, y: Y, r: R, rotate: A, symbol: S} = channels;
const {r, rotate, symbol} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
const circle = this.symbol === symbolCircle;
const circle = symbol === symbolCircle;
const size = R ? undefined : r * r * Math.PI;
if (negative(r)) index = [];
return create("svg:g", context)
Expand Down
6 changes: 3 additions & 3 deletions src/marks/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export function textY(data, options = {}) {
return new Text(data, maybeIntervalMidX({...remainingOptions, y}));
}

function applyIndirectTextStyles(selection, mark, T) {
export function applyIndirectTextStyles(selection, mark, T) {
applyAttr(selection, "text-anchor", mark.textAnchor);
applyAttr(selection, "font-family", mark.fontFamily);
applyAttr(selection, "font-size", mark.fontSize);
Expand All @@ -187,7 +187,7 @@ function applyIndirectTextStyles(selection, mark, T) {
}

function inferFontVariant(T) {
return isNumeric(T) || isTemporal(T) ? "tabular-nums" : undefined;
return T && (isNumeric(T) || isTemporal(T)) ? "tabular-nums" : undefined;
}

// https://developer.mozilla.org/en-US/docs/Web/CSS/font-size
Expand Down Expand Up @@ -444,7 +444,7 @@ function clipper({monospace, lineWidth, textOverflow}) {
// given width, returns [-1, 0]. If the text needs cutting, the given inset
// specifies how much space (in the same units as width and widthof) to reserve
// for a possible ellipsis character.
function cut(text, width, widthof, inset) {
export function cut(text, width, widthof, inset) {
const I = []; // indexes of read character boundaries
let w = 0; // current line width
for (let i = 0, j = 0, n = text.length; i < n; i = j) {
Expand Down
57 changes: 57 additions & 0 deletions src/marks/tooltip.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type {ChannelValueSpec} from "../channel.js";
import type {Data, FrameAnchor, MarkOptions, RenderableMark} from "../mark.js";
import type {TextOptions} from "./text.js";

/** Options for styling text. TODO Move to TextOptions? */
type TextStyles = Pick<TextOptions, "lineHeight" | "lineWidth" | "monospace" | "fontFamily" | "fontSize" | "fontStyle" | "fontVariant" | "fontWeight">; // prettier-ignore

/** Options for the tooltip mark. */
export interface TooltipOptions extends MarkOptions, TextStyles {
/**
* The horizontal position channel specifying the tooltip’s anchor, typically
* bound to the *x* scale.
*/
x?: ChannelValueSpec;

/**
* The vertical position channel specifying the tooltip’s anchor, typically
* bound to the *y* scale.
*/
y?: ChannelValueSpec;

/**
* The frame anchor specifies defaults for **x** and **y** based on the plot’s
* frame; it may be one of the four sides (*top*, *right*, *bottom*, *left*),
* one of the four corners (*top-left*, *top-right*, *bottom-right*,
* *bottom-left*), or the *middle* of the frame. For example, for tooltips
* distributed horizontally at the top of the frame:
*
* ```js
* Plot.tooltip(data, {x: "date", frameAnchor: "top"})
* ```
*/
frameAnchor?: FrameAnchor;

/** TODO */
maxRadius?: number;

/** TODO */
axis?: "x" | "y" | "xy";

/** TODO */
anchor?: "top-left" | "top-right" | "bottom-right" | "bottom-left";
}

/**
* Returns a new tooltip mark for the given *data* and *options*.
*
* If either **x** or **y** is not specified, the default is determined by the
* **frameAnchor** option. If none of **x**, **y**, and **frameAnchor** are
* specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*,
* *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** =
* [*y₀*, *y₁*, *y₂*, …].
*/
export function tooltip(data?: Data, options?: TooltipOptions): Tooltip;

/** The tooltip mark. */
export class Tooltip extends RenderableMark {}
Loading