Skip to content

tooltip: true #1525

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 1 commit into from
Closed
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
7 changes: 4 additions & 3 deletions src/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import {registry} from "./scales/index.js";
import {isSymbol, maybeSymbol} from "./symbol.js";
import {maybeReduce} from "./transforms/group.js";

export function createChannel(data, {scale, type, value, filter, hint}, name) {
if (hint === undefined && typeof value?.transform === "function") hint = value.hint;
export function createChannel(data, channel, name) {
if (channel.alias) return channel;
const {scale, type, value, filter, hint} = channel;
return inferChannelScale(name, {
scale,
type,
value: valueof(data, value),
label: labelof(value),
filter,
hint
hint: hint === undefined && typeof value?.transform === "function" ? value.hint : hint
});
}

Expand Down
3 changes: 3 additions & 0 deletions src/mark.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,9 @@ export interface MarkOptions {
* by an **initializer** to declare extra channels.
*/
channels?: Channels;

/** TODO */
tooltip?: boolean;
}

/** The abstract base class for Mark implementations. */
Expand Down
10 changes: 7 additions & 3 deletions src/mark.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export class Mark {
fx,
fy,
sort,
tooltip,
tooltipAxis,
dx = 0,
dy = 0,
margin = 0,
Expand All @@ -25,6 +27,8 @@ export class Mark {
channels: extraChannels
} = options;
this.data = data;
this.tooltip = !!tooltip;
this.tooltipAxis = tooltipAxis; // TODO validate
this.sort = isDomainSort(sort) ? sort : null;
this.initializer = initializer(options).initializer;
this.transform = this.initializer ? options.transform : basic(options).transform;
Expand All @@ -37,7 +41,7 @@ export class Mark {
}
this.facetAnchor = maybeFacetAnchor(facetAnchor);
channels = maybeNamed(channels);
if (extraChannels !== undefined) channels = {...maybeNamed(extraChannels), ...channels};
if (extraChannels !== undefined) channels = {...channels, ...maybeNamed(extraChannels)};
if (defaults !== undefined) channels = {...styles(this, options, defaults), ...channels};
this.channels = Object.fromEntries(
Object.entries(channels)
Expand All @@ -54,8 +58,8 @@ export class Mark {
}
return [name, channel];
})
.filter(([name, {value, optional}]) => {
if (value != null) return true;
.filter(([name, {alias, value, optional}]) => {
if (value != null || alias) return true;
if (optional) return false;
throw new Error(`missing channel value: ${name}`);
})
Expand Down
34 changes: 33 additions & 1 deletion src/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {createLegends, exposeLegends} from "./legends.js";
import {Mark} from "./mark.js";
import {axisFx, axisFy, axisX, axisY, gridFx, gridFy, gridX, gridY} from "./marks/axis.js";
import {frame} from "./marks/frame.js";
import {tooltip} from "./marks/tooltip.js";
import {arrayify, isColor, isIterable, isNone, isScaleOptions, map, yes, maybeIntervalTransform} from "./options.js";
import {createScales, createScaleFunctions, autoScaleRange, exposeScales} from "./scales.js";
import {innerDimensions, outerDimensions} from "./scales.js";
Expand All @@ -21,7 +22,7 @@ export function plot(options = {}) {
const className = maybeClassName(options.className);

// Flatten any nested marks.
const marks = options.marks === undefined ? [] : flatMarks(options.marks);
const marks = options.marks === undefined ? [] : inferTooltip(flatMarks(options.marks));

// Compute the top-level facet state. This has roughly the same structure as
// mark-specific facet state, except there isn’t a facetsIndex, and there’s a
Expand Down Expand Up @@ -127,6 +128,7 @@ export function plot(options = {}) {
if (stateByMark.has(mark)) throw new Error("duplicate mark; each mark must be unique");
const {facetsIndex, channels: facetChannels} = facetStateByMark.get(mark) ?? {};
const {data, facets, channels} = mark.initialize(facetsIndex, facetChannels, options);
resolveChannelAliases(channels, stateByMark);
applyScaleTransforms(channels, options);
stateByMark.set(mark, {data, facets, channels});
}
Expand Down Expand Up @@ -338,6 +340,36 @@ function flatMarks(marks) {
.map(markify);
}

// Note: Mutates marks!
function inferTooltip(marks) {
for (const mark of marks) {
if (mark.tooltip) {
marks.push(tooltip(mark.data, tooltipOptions(mark)));
break;
}
}
return marks;
}

function tooltipOptions(mark) {
const {tooltipAxis: axis, facet, facetAnchor, fx, fy} = mark;
return {axis, x: null, facet, facetAnchor, fx, fy, channels: tooltipChannels(mark)};
}

function tooltipChannels(mark) {
return Object.fromEntries(Object.keys(mark.channels).map((name) => [name, {alias: mark}]));
}

// Note: mutates channels!
function resolveChannelAliases(channels, stateByMark) {
for (const name in channels) {
const channel = channels[name];
if (channel.alias) {
channels[name] = stateByMark.get(channel.alias).channels[name];
}
}
}

function markify(mark) {
return typeof mark.render === "function" ? mark : new Render(mark);
}
Expand Down
16 changes: 8 additions & 8 deletions src/transforms/stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,54 @@ export function stackX(stackOptions = {}, options = {}) {
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
const {y1, y = y1, x, ...rest} = options; // note: consumes x!
const [transform, Y, x1, x2] = stack(y, x, "y", "x", stackOptions, rest);
return {...transform, y1, y: Y, x1, x2, x: mid(x1, x2)};
return {...transform, y1, y: Y, x1, x2, x: mid(x1, x2), tooltipAxis: "y"};
}

export function stackX1(stackOptions = {}, options = {}) {
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
const {y1, y = y1, x} = options;
const [transform, Y, X] = stack(y, x, "y", "x", stackOptions, options);
return {...transform, y1, y: Y, x: X};
return {...transform, y1, y: Y, x: X, tooltipAxis: "y"};
}

export function stackX2(stackOptions = {}, options = {}) {
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
const {y1, y = y1, x} = options;
const [transform, Y, , X] = stack(y, x, "y", "x", stackOptions, options);
return {...transform, y1, y: Y, x: X};
return {...transform, y1, y: Y, x: X, tooltipAxis: "y"};
}

export function stackY(stackOptions = {}, options = {}) {
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
const {x1, x = x1, y, ...rest} = options; // note: consumes y!
const [transform, X, y1, y2] = stack(x, y, "x", "y", stackOptions, rest);
return {...transform, x1, x: X, y1, y2, y: mid(y1, y2)};
return {...transform, x1, x: X, y1, y2, y: mid(y1, y2), tooltipAxis: "x"};
}

export function stackY1(stackOptions = {}, options = {}) {
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
const {x1, x = x1, y} = options;
const [transform, X, Y] = stack(x, y, "x", "y", stackOptions, options);
return {...transform, x1, x: X, y: Y};
return {...transform, x1, x: X, y: Y, tooltipAxis: "x"};
}

export function stackY2(stackOptions = {}, options = {}) {
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
const {x1, x = x1, y} = options;
const [transform, X, , Y] = stack(x, y, "x", "y", stackOptions, options);
return {...transform, x1, x: X, y: Y};
return {...transform, x1, x: X, y: Y, tooltipAxis: "x"};
}

export function maybeStackX({x, x1, x2, ...options} = {}) {
if (x1 === undefined && x2 === undefined) return stackX({x, ...options});
[x1, x2] = maybeZero(x, x1, x2);
return {...options, x1, x2};
return {...options, x1, x2, tooltipAxis: "x"};
}

export function maybeStackY({y, y1, y2, ...options} = {}) {
if (y1 === undefined && y2 === undefined) return stackY({y, ...options});
[y1, y2] = maybeZero(y, y1, y2);
return {...options, y1, y2};
return {...options, y1, y2, tooltipAxis: "y"};
}

// The reverse option is ambiguous: it is both a stack option and a basic
Expand Down
4 changes: 2 additions & 2 deletions test/plots/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export async function tooltipBin() {
const olympians = await d3.csv<any>("data/athletes.csv", d3.autoType);
return Plot.plot({
marks: [
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight"})),
Plot.tooltip(olympians, Plot.binX({y: "count"}, {x: "weight", axis: "x"}))
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", tooltip: true})),
// Plot.tooltip(olympians, Plot.binX({y: "count"}, {x: "weight", axis: "x"}))
]
});
}
Expand Down