Skip to content

dataset mark option #2295

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
5 changes: 4 additions & 1 deletion docs/features/marks.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ All marks support the following optional channels:
* **title** - an accessible, short-text description (a string of text, possibly with newlines)
* **href** - a URL to link to
* **ariaLabel** - a short label representing the value in the accessibility tree
* **dataset** - the [dataset property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) <VersionBadge pr="2295" />

The **fill**, **fillOpacity**, **stroke**, **strokeWidth**, **strokeOpacity**, and **opacity** options can be specified as either channels or constants. When the fill or stroke is specified as a function or array, it is interpreted as a channel; when the fill or stroke is specified as a string, it is interpreted as a constant if a valid CSS color and otherwise it is interpreted as a column name for a channel. Similarly when the fill opacity, stroke opacity, object opacity, stroke width, or radius is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel.

Expand All @@ -533,7 +534,9 @@ In addition to functions of data, arrays, and column names, channel values can b
Plot.dot(numbers, {x: {transform: (data) => data}})
```

The **title**, **href**, and **ariaLabel** options can *only* be specified as channels. When these options are specified as a string, the string refers to the name of a column in the mark’s associated data. If you’d like every instance of a particular mark to have the same value, specify the option as a function that returns the desired value, *e.g.* `() => "Hello, world!"`.
The **title**, **href**, **ariaLabel**, and **dataset** options can *only* be specified as channels. When these options are specified as a string, the string refers to the name of a column in the mark’s associated data. If you’d like every instance of a particular mark to have the same value, specify the option as a function that returns the desired value, *e.g.* `() => "Hello, world!"`.

When the **dataset** channel contains boolean, number, string or date values, they are applied as a `data-{key}` property, where the key is the channel’s label if present, and otherwise defaults to "value". When the values are objects, each entry is applied individually as a `data-{key}` property. Values are coerced to strings, with dates in [short ISO format](https://github.com/mbostock/isoformat). Keys must follow the naming specification for [custom data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes).

For marks that support the **frameAnchor** option, it may be specified as 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.

Expand Down
15 changes: 15 additions & 0 deletions src/mark.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,21 @@ export interface MarkOptions {
*/
target?: string;

/**
* A [dataset][1] for the mark; a channel specifying arbitrary data. When the
* **dataset** channel contains boolean, number, string or date values, they
* are applied as a `data-{key}` property, where the key is the channel’s
* label if present, and otherwise defaults to "value". When the values are
* objects, each entry is applied individually as a `data-{key}` property.
* Values are coerced to strings, with dates in [short ISO format][2]. Keys
* must follow the naming specification for [custom data attributes][3].
*
* [1]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset
* [2]: https://github.com/mbostock/isoformat
* [3]: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
*/
dataset?: ChannelValueSpec;

/**
* An object defining additional custom channels. This meta option may be used
* by an **initializer** to declare extra channels.
Expand Down
30 changes: 25 additions & 5 deletions src/style.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {group, namespaces, select} from "d3";
import {create} from "./context.js";
import {defined, nonempty} from "./defined.js";
import {formatDefault} from "./format.js";
import {formatDefault, formatIsoDate} from "./format.js";
import {isNone, isNoneish, isRound, maybeColorChannel, maybeNumberChannel} from "./options.js";
import {keyof, number, string} from "./options.js";
import {warn} from "./warnings.js";
Expand Down Expand Up @@ -44,7 +44,8 @@ export function styles(
paintOrder,
pointerEvents,
shapeRendering,
channels
channels,
dataset
},
{
ariaLabel: cariaLabel,
Expand Down Expand Up @@ -137,6 +138,7 @@ export function styles(
mark.paintOrder = impliedString(paintOrder, "normal");
mark.pointerEvents = impliedString(pointerEvents, "auto");
mark.shapeRendering = impliedString(shapeRendering, "auto");
mark.datakey = typeof dataset === "string" ? dataset : dataset?.label;

return {
title: {value: title, optional: true, filter: null},
Expand All @@ -147,7 +149,8 @@ export function styles(
stroke: {value: vstroke, scale: "auto", optional: true},
strokeOpacity: {value: vstrokeOpacity, scale: "auto", optional: true},
strokeWidth: {value: vstrokeWidth, optional: true},
opacity: {value: vopacity, scale: "auto", optional: true}
opacity: {value: vopacity, scale: "auto", optional: true},
dataset: {value: dataset, optional: true, filter: null}
};
}

Expand Down Expand Up @@ -179,7 +182,7 @@ export function applyTextGroup(selection, T) {

export function applyChannelStyles(
selection,
{target, tip},
{target, tip, datakey},
{
ariaLabel: AL,
title: T,
Expand All @@ -189,7 +192,8 @@ export function applyChannelStyles(
strokeOpacity: SO,
strokeWidth: SW,
opacity: O,
href: H
href: H,
dataset: D
}
) {
if (AL) applyAttr(selection, "aria-label", (i) => AL[i]);
Expand All @@ -200,6 +204,7 @@ export function applyChannelStyles(
if (SW) applyAttr(selection, "stroke-width", (i) => SW[i]);
if (O) applyAttr(selection, "opacity", (i) => O[i]);
if (H) applyHref(selection, (i) => H[i], target);
if (D) applyDataset(selection, (i) => D[i], datakey);
if (!tip) applyTitle(selection, T);
}

Expand Down Expand Up @@ -422,6 +427,21 @@ export function applyTransform(selection, mark, {x, y}, tx = offset, ty = offset
if (tx || ty) selection.attr("transform", `translate(${tx},${ty})`);
}

export function applyDataset(selection, value, keyname = "value") {
selection.each(function (i) {
const V = value(i);
if (V == null) return;
const O =
typeof V === "number" || typeof V === "boolean" || typeof V === "string" || V instanceof Date
? {[keyname]: V}
: V;
if (typeof O !== "object") throw new Error(`Unsupported dataset property: ${value}`);
for (const [key, v] of Object.entries(O)) {
this.setAttribute(`data-${key}`, v instanceof Date ? formatIsoDate(v) : String(v));
}
});
}

export function impliedString(value, impliedValue) {
if ((value = string(value)) !== impliedValue) return value;
}
Expand Down
155 changes: 155 additions & 0 deletions test/output/dataset.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading