Skip to content

Commit c9a8b75

Browse files
committed
dataset mark option
1 parent 90a5689 commit c9a8b75

File tree

7 files changed

+310
-6
lines changed

7 files changed

+310
-6
lines changed

docs/features/marks.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ All marks support the following optional channels:
510510
* **title** - an accessible, short-text description (a string of text, possibly with newlines)
511511
* **href** - a URL to link to
512512
* **ariaLabel** - a short label representing the value in the accessibility tree
513+
* **dataset** - the [dataset property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) <VersionBadge pr="2295" />
513514

514515
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.
515516

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

536-
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!"`.
537+
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!"`.
538+
539+
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).
537540

538541
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.
539542

src/mark.d.ts

+15
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,21 @@ export interface MarkOptions {
466466
*/
467467
target?: string;
468468

469+
/**
470+
* A [dataset][1] for the mark; a channel specifying arbitrary data. When the
471+
* **dataset** channel contains boolean, number, string or date values, they
472+
* are applied as a `data-{key}` property, where the key is the channel’s
473+
* label if present, and otherwise defaults to "value". When the values are
474+
* objects, each entry is applied individually as a `data-{key}` property.
475+
* Values are coerced to strings, with dates in [short ISO format][2]. Keys
476+
* must follow the naming specification for [custom data attributes][3].
477+
*
478+
* [1]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset
479+
* [2]: https://github.com/mbostock/isoformat
480+
* [3]: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
481+
*/
482+
dataset?: ChannelValueSpec;
483+
469484
/**
470485
* An object defining additional custom channels. This meta option may be used
471486
* by an **initializer** to declare extra channels.

src/style.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {group, namespaces, select} from "d3";
22
import {create} from "./context.js";
33
import {defined, nonempty} from "./defined.js";
4-
import {formatDefault} from "./format.js";
4+
import {formatDefault, formatIsoDate} from "./format.js";
55
import {isNone, isNoneish, isRound, maybeColorChannel, maybeNumberChannel} from "./options.js";
66
import {keyof, number, string} from "./options.js";
77
import {warn} from "./warnings.js";
@@ -44,7 +44,8 @@ export function styles(
4444
paintOrder,
4545
pointerEvents,
4646
shapeRendering,
47-
channels
47+
channels,
48+
dataset
4849
},
4950
{
5051
ariaLabel: cariaLabel,
@@ -137,6 +138,7 @@ export function styles(
137138
mark.paintOrder = impliedString(paintOrder, "normal");
138139
mark.pointerEvents = impliedString(pointerEvents, "auto");
139140
mark.shapeRendering = impliedString(shapeRendering, "auto");
141+
mark.datakey = typeof dataset === "string" ? dataset : dataset?.label;
140142

141143
return {
142144
title: {value: title, optional: true, filter: null},
@@ -147,7 +149,8 @@ export function styles(
147149
stroke: {value: vstroke, scale: "auto", optional: true},
148150
strokeOpacity: {value: vstrokeOpacity, scale: "auto", optional: true},
149151
strokeWidth: {value: vstrokeWidth, optional: true},
150-
opacity: {value: vopacity, scale: "auto", optional: true}
152+
opacity: {value: vopacity, scale: "auto", optional: true},
153+
dataset: {value: dataset, optional: true, filter: null}
151154
};
152155
}
153156

@@ -179,7 +182,7 @@ export function applyTextGroup(selection, T) {
179182

180183
export function applyChannelStyles(
181184
selection,
182-
{target, tip},
185+
{target, tip, datakey},
183186
{
184187
ariaLabel: AL,
185188
title: T,
@@ -189,7 +192,8 @@ export function applyChannelStyles(
189192
strokeOpacity: SO,
190193
strokeWidth: SW,
191194
opacity: O,
192-
href: H
195+
href: H,
196+
dataset: D
193197
}
194198
) {
195199
if (AL) applyAttr(selection, "aria-label", (i) => AL[i]);
@@ -200,6 +204,7 @@ export function applyChannelStyles(
200204
if (SW) applyAttr(selection, "stroke-width", (i) => SW[i]);
201205
if (O) applyAttr(selection, "opacity", (i) => O[i]);
202206
if (H) applyHref(selection, (i) => H[i], target);
207+
if (D) applyDataset(selection, (i) => D[i], datakey);
203208
if (!tip) applyTitle(selection, T);
204209
}
205210

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

430+
export function applyDataset(selection, value, keyname = "value") {
431+
selection.each(function (i) {
432+
const V = value(i);
433+
if (V == null) return;
434+
const O =
435+
typeof V === "number" || typeof V === "boolean" || typeof V === "string" || V instanceof Date
436+
? {[keyname]: V}
437+
: V;
438+
if (typeof O !== "object") throw new Error(`Unsupported dataset property: ${value}`);
439+
for (const [key, v] of Object.entries(O)) {
440+
this.setAttribute(`data-${key}`, v instanceof Date ? formatIsoDate(v) : String(v));
441+
}
442+
});
443+
}
444+
425445
export function impliedString(value, impliedValue) {
426446
if ((value = string(value)) !== impliedValue) return value;
427447
}

test/output/dataset.svg

+155
Loading

0 commit comments

Comments
 (0)