Skip to content

Commit

Permalink
href channel (#645)
Browse files Browse the repository at this point in the history
* href channel

* apply href and target to the line and area marks
document

* Update README

* test plot for href (#647)

* cheeky test plot for href

* tweak test; add snapshot

Co-authored-by: Mike Bostock <[email protected]>

Co-authored-by: Philippe Rivière <[email protected]>
  • Loading branch information
mbostock and Fil authored Jan 2, 2022
1 parent eb09fb7 commit 9c84cdd
Show file tree
Hide file tree
Showing 16 changed files with 267 additions and 25 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ All marks support the following style options:
* **shapeRendering** - the [shape-rendering mode](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering) (*e.g.*, *crispEdges*)
* **dx** - horizontal offset (in pixels; defaults to 0)
* **dy** - vertical offset (in pixels; defaults to 0)
* **target** - link target (e.g., “_blank” for a new window); for use with the **href** channel

For all marks except [text](#plottextdata-options), the **dx** and **dy** options are rendered as a transform property, possibly including a 0.5px offset on low-density screens.

Expand All @@ -623,6 +624,7 @@ All marks support the following optional channels:
* **strokeWidth** - a stroke width (in pixels)
* **opacity** - an object opacity; bound to the *opacity* scale
* **title** - a tooltip (a string of text, possibly with newlines)
* **href** - a URL to link to

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 Down
2 changes: 1 addition & 1 deletion src/marks/area.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class Area extends Mark {
.data(Z ? group(I, i => Z[i]).values() : [I])
.join("path")
.call(applyDirectStyles, this)
.call(applyGroupedChannelStyles, channels)
.call(applyGroupedChannelStyles, this, channels)
.attr("d", shapeArea()
.curve(this.curve)
.defined(i => defined(X1[i]) && defined(Y1[i]) && defined(X2[i]) && defined(Y2[i]))
Expand Down
2 changes: 1 addition & 1 deletion src/marks/bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class AbstractBar extends Mark {
.attr("height", this._height(scales, channels, dimensions))
.call(applyAttr, "rx", rx)
.call(applyAttr, "ry", ry)
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
_x(scales, {x: X}, {marginLeft}) {
Expand Down
2 changes: 1 addition & 1 deletion src/marks/dot.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class Dot extends Mark {
.attr("cx", X ? i => X[i] : (marginLeft + width - marginRight) / 2)
.attr("cy", Y ? i => Y[i] : (marginTop + height - marginBottom) / 2)
.attr("r", R ? i => R[i] : this.r)
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/marks/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class Image extends Mark {
.call(applyAttr, "href", S ? i => S[i] : this.src)
.call(applyAttr, "preserveAspectRatio", this.preserveAspectRatio)
.call(applyAttr, "crossorigin", this.crossOrigin)
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/marks/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class Line extends Mark {
.data(Z ? group(I, i => Z[i]).values() : [I])
.join("path")
.call(applyDirectStyles, this)
.call(applyGroupedChannelStyles, channels)
.call(applyGroupedChannelStyles, this, channels)
.attr("d", shapeLine()
.curve(this.curve)
.defined(i => defined(X[i]) && defined(Y[i]))
Expand Down
2 changes: 1 addition & 1 deletion src/marks/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Link extends Mark {
c.lineEnd();
return `${p}`;
})
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/marks/rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class Rect extends Mark {
.attr("height", Y1 && Y2 && !isCollapsed(y) ? i => Math.max(0, Math.abs(Y1[i] - Y2[i]) - insetTop - insetBottom) : height - marginTop - marginBottom - insetTop - insetBottom)
.call(applyAttr, "rx", rx)
.call(applyAttr, "ry", ry)
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/marks/rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class RuleX extends Mark {
.attr("x2", X ? i => X[i] : (marginLeft + width - marginRight) / 2)
.attr("y1", Y1 && !isCollapsed(y) ? i => Y1[i] + insetTop : marginTop + insetTop)
.attr("y2", Y2 && !isCollapsed(y) ? (y.bandwidth ? i => Y2[i] + y.bandwidth() - insetBottom : i => Y2[i] - insetBottom) : height - marginBottom - insetBottom)
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
}
Expand Down Expand Up @@ -93,7 +93,7 @@ export class RuleY extends Mark {
.attr("x2", X2 && !isCollapsed(x) ? (x.bandwidth ? i => X2[i] + x.bandwidth() - insetRight : i => X2[i] - insetRight) : width - marginRight - insetRight)
.attr("y1", Y ? i => Y[i] : (marginTop + height - marginBottom) / 2)
.attr("y2", Y ? i => Y[i] : (marginTop + height - marginBottom) / 2)
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/marks/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class Text extends Mark {
: text => text.attr("x", X ? i => X[i] : cx).attr("y", Y ? i => Y[i] : cy))
.call(applyAttr, "font-size", FS && (i => FS[i]))
.text(i => T[i])
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/marks/tick.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class AbstractTick extends Mark {
.attr("x2", this._x2(scales, channels, dimensions))
.attr("y1", this._y1(scales, channels, dimensions))
.attr("y2", this._y2(scales, channels, dimensions))
.call(applyChannelStyles, channels))
.call(applyChannelStyles, this, channels))
.node();
}
}
Expand Down
47 changes: 33 additions & 14 deletions src/style.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {namespaces} from "d3";
import {string, number, maybeColor, maybeNumber, title, titleGroup} from "./mark.js";
import {filter} from "./defined.js";

Expand All @@ -7,6 +8,8 @@ export function styles(
mark,
{
title,
href,
target,
fill,
fillOpacity,
stroke,
Expand Down Expand Up @@ -87,13 +90,15 @@ export function styles(
mark.strokeDasharray = string(strokeDasharray);
}

mark.target = string(target);
mark.opacity = impliedNumber(copacity, 1);
mark.mixBlendMode = impliedString(mixBlendMode, "normal");
mark.shapeRendering = impliedString(shapeRendering, "auto");

return [
...channels,
{name: "title", value: title, optional: true},
{name: "href", value: href, optional: true},
{name: "fill", value: vfill, scale: "color", optional: true},
{name: "fillOpacity", value: vfillOpacity, scale: "opacity", optional: true},
{name: "stroke", value: vstroke, scale: "color", optional: true},
Expand All @@ -103,23 +108,25 @@ export function styles(
];
}

export function applyChannelStyles(selection, {title: L, fill: F, fillOpacity: FO, stroke: S, strokeOpacity: SO, strokeWidth: SW, opacity: O}) {
applyAttr(selection, "fill", F && (i => F[i]));
applyAttr(selection, "fill-opacity", FO && (i => FO[i]));
applyAttr(selection, "stroke", S && (i => S[i]));
applyAttr(selection, "stroke-opacity", SO && (i => SO[i]));
applyAttr(selection, "stroke-width", SW && (i => SW[i]));
applyAttr(selection, "opacity", O && (i => O[i]));
export function applyChannelStyles(selection, {target}, {title: L, fill: F, fillOpacity: FO, stroke: S, strokeOpacity: SO, strokeWidth: SW, opacity: O, href: H}) {
if (F) applyAttr(selection, "fill", i => F[i]);
if (FO) applyAttr(selection, "fill-opacity", i => FO[i]);
if (S) applyAttr(selection, "stroke", i => S[i]);
if (SO) applyAttr(selection, "stroke-opacity", i => SO[i]);
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);
title(L)(selection);
}

export function applyGroupedChannelStyles(selection, {title: L, fill: F, fillOpacity: FO, stroke: S, strokeOpacity: SO, strokeWidth: SW, opacity: O}) {
applyAttr(selection, "fill", F && (([i]) => F[i]));
applyAttr(selection, "fill-opacity", FO && (([i]) => FO[i]));
applyAttr(selection, "stroke", S && (([i]) => S[i]));
applyAttr(selection, "stroke-opacity", SO && (([i]) => SO[i]));
applyAttr(selection, "stroke-width", SW && (([i]) => SW[i]));
applyAttr(selection, "opacity", O && (([i]) => O[i]));
export function applyGroupedChannelStyles(selection, {target}, {title: L, fill: F, fillOpacity: FO, stroke: S, strokeOpacity: SO, strokeWidth: SW, opacity: O, href: H}) {
if (F) applyAttr(selection, "fill", ([i]) => F[i]);
if (FO) applyAttr(selection, "fill-opacity", ([i]) => FO[i]);
if (S) applyAttr(selection, "stroke", ([i]) => S[i]);
if (SO) applyAttr(selection, "stroke-opacity", ([i]) => SO[i]);
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);
titleGroup(L)(selection);
}

Expand All @@ -141,6 +148,18 @@ export function applyDirectStyles(selection, mark) {
applyAttr(selection, "opacity", mark.opacity);
}

function applyHref(selection, href, target) {
selection.each(function(i) {
const h = href(i);
if (h != null) {
const a = document.createElementNS(namespaces.svg, "a");
a.setAttributeNS(namespaces.xlink, "href", h);
if (target != null) a.setAttribute("target", target);
this.parentNode.insertBefore(a, this).appendChild(this);
}
});
}

export function applyAttr(selection, name, value) {
if (value != null) selection.attr(name, value);
}
Expand Down
1 change: 1 addition & 0 deletions test/data/plot-documentation.json

Large diffs are not rendered by default.

Loading

0 comments on commit 9c84cdd

Please sign in to comment.