From 9c84cdd6be9b2726c5dffa315965f84c33a82d7e Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sun, 2 Jan 2022 12:23:06 -0800 Subject: [PATCH] href channel (#645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: Philippe Rivière --- README.md | 2 + src/marks/area.js | 2 +- src/marks/bar.js | 2 +- src/marks/dot.js | 2 +- src/marks/image.js | 2 +- src/marks/line.js | 2 +- src/marks/link.js | 2 +- src/marks/rect.js | 2 +- src/marks/rule.js | 4 +- src/marks/text.js | 2 +- src/marks/tick.js | 2 +- src/style.js | 47 +++++--- test/data/plot-documentation.json | 1 + test/output/documentationLinks.svg | 185 +++++++++++++++++++++++++++++ test/plots/documentation-links.js | 34 ++++++ test/plots/index.js | 1 + 16 files changed, 267 insertions(+), 25 deletions(-) create mode 100644 test/data/plot-documentation.json create mode 100644 test/output/documentationLinks.svg create mode 100644 test/plots/documentation-links.js diff --git a/README.md b/README.md index a5ee7025d1..3bc7548047 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. diff --git a/src/marks/area.js b/src/marks/area.js index d4531388d4..dca3302aff 100644 --- a/src/marks/area.js +++ b/src/marks/area.js @@ -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])) diff --git a/src/marks/bar.js b/src/marks/bar.js index 07b5b4948c..7ac336f5f1 100644 --- a/src/marks/bar.js +++ b/src/marks/bar.js @@ -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}) { diff --git a/src/marks/dot.js b/src/marks/dot.js index 2fdb88a3e7..6f11c623ac 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.js @@ -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(); } } diff --git a/src/marks/image.js b/src/marks/image.js index ac992ab745..302b59b535 100644 --- a/src/marks/image.js +++ b/src/marks/image.js @@ -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(); } } diff --git a/src/marks/line.js b/src/marks/line.js index 6630592993..f8be691af3 100644 --- a/src/marks/line.js +++ b/src/marks/line.js @@ -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])) diff --git a/src/marks/link.js b/src/marks/link.js index 5c36418def..86acb22dc0 100644 --- a/src/marks/link.js +++ b/src/marks/link.js @@ -46,7 +46,7 @@ export class Link extends Mark { c.lineEnd(); return `${p}`; }) - .call(applyChannelStyles, channels)) + .call(applyChannelStyles, this, channels)) .node(); } } diff --git a/src/marks/rect.js b/src/marks/rect.js index c06f53c27a..849b6c1dcd 100644 --- a/src/marks/rect.js +++ b/src/marks/rect.js @@ -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(); } } diff --git a/src/marks/rule.js b/src/marks/rule.js index cab950245d..2ca13bbcff 100644 --- a/src/marks/rule.js +++ b/src/marks/rule.js @@ -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(); } } @@ -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(); } } diff --git a/src/marks/text.js b/src/marks/text.js index 7666df16e3..e0a3a748b3 100644 --- a/src/marks/text.js +++ b/src/marks/text.js @@ -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(); } } diff --git a/src/marks/tick.js b/src/marks/tick.js index 429ee08928..09740e7282 100644 --- a/src/marks/tick.js +++ b/src/marks/tick.js @@ -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(); } } diff --git a/src/style.js b/src/style.js index 625bebd201..e4d8a46b49 100644 --- a/src/style.js +++ b/src/style.js @@ -1,3 +1,4 @@ +import {namespaces} from "d3"; import {string, number, maybeColor, maybeNumber, title, titleGroup} from "./mark.js"; import {filter} from "./defined.js"; @@ -7,6 +8,8 @@ export function styles( mark, { title, + href, + target, fill, fillOpacity, stroke, @@ -87,6 +90,7 @@ 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"); @@ -94,6 +98,7 @@ export function styles( 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}, @@ -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); } @@ -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); } diff --git a/test/data/plot-documentation.json b/test/data/plot-documentation.json new file mode 100644 index 0000000000..f1d2074c7e --- /dev/null +++ b/test/data/plot-documentation.json @@ -0,0 +1 @@ +{"id":"febc26eef760de7c","type":"public","slug":"plot","title":"Observable Plot","description":"A concise API for exploratory data visualization","update_time":"2021-05-04T05:19:06.423Z","pinned":false,"ordered":true,"custom_thumbnail":"64f414fef8a91248865f5759641b0cf537bc87c0aaf57dc368ffe673013eccaa","default_thumbnail":"64f414fef8a91248865f5759641b0cf537bc87c0aaf57dc368ffe673013eccaa","thumbnail":"64f414fef8a91248865f5759641b0cf537bc87c0aaf57dc368ffe673013eccaa","listing_count":25,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","type":"team"},"listings":[{"id":"f3d342db2d382751","type":"notebook_reader","publish_level":"public","version":739,"publish_version":739,"title":"Observable Plot","update_time":"2021-12-07T20:36:55.186Z","publish_time":"2021-05-04T05:14:55.223Z","likes":296,"comment_count":5,"slug":"plot","thumbnail":"64f414fef8a91248865f5759641b0cf537bc87c0aaf57dc368ffe673013eccaa","default_thumbnail":null,"fork_of":false,"collection_count":2,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"e6a8fdd26dcec9fe","type":"notebook_reader","publish_level":"public","version":932,"publish_version":932,"title":"Marks / Observable Plot","update_time":"2021-05-13T23:12:58.703Z","publish_time":"2021-05-04T05:15:04.229Z","likes":16,"comment_count":0,"slug":"plot-marks","thumbnail":"17e04344c0f0cee103a18bfd0853f6af30baa28f0439d19dc231a473373ab8f5","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"859f77bf8cde8dc0","type":"notebook_reader","publish_level":"public","version":1535,"publish_version":1535,"title":"Scales / Observable Plot","update_time":"2021-08-21T19:38:05.959Z","publish_time":"2021-05-04T05:15:12.433Z","likes":18,"comment_count":15,"slug":"plot-scales","thumbnail":"fc104fd0099a7333f000901b3d9a38e4b4f8a9e8bac40ffd746aab1cc9f21fa5","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"2e3ea65af40a30c2","type":"notebook_reader","publish_level":"public","version":197,"publish_version":197,"title":"Transforms / Observable Plot","update_time":"2021-11-09T15:58:08.992Z","publish_time":"2021-05-04T05:15:21.061Z","likes":6,"comment_count":0,"slug":"plot-transforms","thumbnail":"c7c6bd4e567a9e3081d3c56e19d91ddc56c4591e7d281ab601188c3018a70c5f","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"171f9752c24bedc4","type":"notebook_reader","publish_level":"public","version":133,"publish_version":133,"title":"Facets / Observable Plot","update_time":"2021-12-08T11:58:00.238Z","publish_time":"2021-05-04T05:15:28.396Z","likes":10,"comment_count":0,"slug":"plot-facets","thumbnail":"e58c30cf3f770a5b5b2c1b1cb76ac20b77dc5364c713158e044324c785233f3e","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"0cd3c783b0b6c48f","type":"notebook_reader","publish_level":"public","version":584,"publish_version":584,"title":"Area Mark / Observable Plot","update_time":"2021-05-14T19:33:30.712Z","publish_time":"2021-05-04T05:15:55.865Z","likes":3,"comment_count":0,"slug":"plot-area","thumbnail":"f0a4487c91350493f1880454fbbdf91c421f9bd8868af4936757be73a8717b7b","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"c79a6352bf756f69","type":"notebook_reader","publish_level":"public","version":1140,"publish_version":1140,"title":"Bar Mark / Observable Plot","update_time":"2021-11-09T03:42:52.808Z","publish_time":"2021-05-04T05:16:01.900Z","likes":9,"comment_count":2,"slug":"plot-bar","thumbnail":"3d378538d99293ba7b7285b0e70fe18009337de7dd829c75dbd6d693517432ca","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"d47bf339d9c06f8c","type":"notebook_reader","publish_level":"public","version":463,"publish_version":463,"title":"Cell Mark / Observable Plot","update_time":"2021-12-08T09:43:39.931Z","publish_time":"2021-05-04T05:16:08.947Z","likes":17,"comment_count":0,"slug":"plot-cell","thumbnail":"b5b820dbad95e6a67205c6b930f07e9cc926bdd5d43e1286146dd86cafaeedef","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"d71820e4e5468c53","type":"notebook_reader","publish_level":"public","version":467,"publish_version":467,"title":"Dot Mark / Observable Plot","update_time":"2021-12-07T17:08:42.294Z","publish_time":"2021-05-04T05:16:16.620Z","likes":5,"comment_count":6,"slug":"plot-dot","thumbnail":"323235720172d6fee0ef5d6168490cbed268c9033ae120b4c91ad2b266bdd184","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"6c80e137f8887344","type":"notebook_reader","publish_level":"public","version":1319,"publish_version":1319,"title":"Image Mark / Observable Plot","update_time":"2021-12-08T09:35:41.926Z","publish_time":"2021-12-07T20:46:02.900Z","likes":11,"comment_count":10,"slug":"plot-image","thumbnail":"460aa771e3208a4e3f45d4ed85c50c85516bca054d4606ea6649a48fcc4f111e","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"45a379fcfcb14253","github_login":"Fil","login":"fil","name":"Fil","bio":"Vocateur.","home_url":"https://visionscarto.net/","avatar_url":"https://avatars0.githubusercontent.com/u/7001?v=4"}},{"id":"73aba1d26c33f35f","type":"notebook_reader","publish_level":"public","version":917,"publish_version":917,"title":"Line Mark / Observable Plot","update_time":"2021-05-13T23:12:38.258Z","publish_time":"2021-05-04T05:16:24.621Z","likes":8,"comment_count":0,"slug":"plot-line","thumbnail":"42bfda8e0c228d0dbc5e661296742a4ec39baf9f9c60c280be85d237e0cd73fc","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"c04b4c0471d3fdf5","type":"notebook_reader","publish_level":"public","version":236,"publish_version":236,"title":"Link Mark / Observable Plot","update_time":"2021-05-13T23:12:31.649Z","publish_time":"2021-05-04T05:16:31.882Z","likes":2,"comment_count":3,"slug":"plot-link","thumbnail":"3eac02b86fdaa4ed37924a8f8c7b582e77211136493f0bccdc1f8153e72d8297","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"1548b29b7b471b5c","type":"notebook_reader","publish_level":"public","version":211,"publish_version":211,"title":"Rect Mark / Observable Plot","update_time":"2021-05-24T21:49:07.963Z","publish_time":"2021-05-04T05:16:38.332Z","likes":2,"comment_count":0,"slug":"plot-rect","thumbnail":"e4bf03d155af21a24533562d7fa68d3de7b69039f83294594bd3db0f7e5fa934","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"43273f5802829607","type":"notebook_reader","publish_level":"public","version":434,"publish_version":434,"title":"Rule Mark / Observable Plot","update_time":"2021-05-13T23:12:25.483Z","publish_time":"2021-05-04T05:16:46.119Z","likes":4,"comment_count":0,"slug":"plot-rule","thumbnail":"06ba94eeed4d9b60d019cf8f0b032e27b4c4936fe3ad13b2dc47b79e871ffcf7","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"cf4f2dc16a7644a4","type":"notebook_reader","publish_level":"public","version":323,"publish_version":323,"title":"Text Mark / Observable Plot","update_time":"2021-12-07T16:01:12.945Z","publish_time":"2021-05-04T05:16:52.624Z","likes":5,"comment_count":4,"slug":"plot-text","thumbnail":"38ce703e6382ebc50f3f9015b5216ce824c312ddca03f7b345823010685fc914","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"fd877a47abae7323","type":"notebook_reader","publish_level":"public","version":151,"publish_version":151,"title":"Tick Mark / Observable Plot","update_time":"2021-09-21T19:52:18.409Z","publish_time":"2021-05-04T05:16:59.035Z","likes":1,"comment_count":4,"slug":"plot-tick","thumbnail":"17ac3016ac7dc061e904156a0c7631abdaa60f541e9127c4c4c4bdef5b06fe3f","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"56a9376ca9fe57f2","type":"notebook_reader","publish_level":"public","version":152,"publish_version":152,"title":"Frame Mark / Observable Plot","update_time":"2021-05-13T23:12:18.317Z","publish_time":"2021-05-04T05:17:04.967Z","likes":3,"comment_count":0,"slug":"plot-frame","thumbnail":"0a551b1d232053a3a8c1bb868e09d1754ad7c054822766b566843a575ab9d183","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"b5991cf5b1ae1899","type":"notebook_reader","publish_level":"public","version":756,"publish_version":756,"title":"Group Transform / Observable Plot","update_time":"2021-05-13T23:12:15.834Z","publish_time":"2021-05-04T05:17:11.446Z","likes":7,"comment_count":0,"slug":"plot-group","thumbnail":"17ae90540a6c2e950dc2625a1217297ed9a177ae48c3535c61893b1b49213990","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"73e66bd6db67c8aa","type":"notebook_reader","publish_level":"public","version":586,"publish_version":586,"title":"Bin Transform / Observable Plot","update_time":"2021-05-18T21:48:22.531Z","publish_time":"2021-05-04T05:17:17.965Z","likes":12,"comment_count":5,"slug":"plot-bin","thumbnail":"6c7912a62a512d88ee539cb621b8d7334b97ef60e60a6437b711aed32ec4f418","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"33bc32700f22adb1","type":"notebook_reader","publish_level":"public","version":845,"publish_version":845,"title":"Stack Transform / Observable Plot","update_time":"2021-12-07T13:48:02.114Z","publish_time":"2021-05-04T05:18:28.790Z","likes":13,"comment_count":0,"slug":"plot-stack","thumbnail":"e9cc1076c63314fb1f644b2eaca629480682a46085c0fa6bf032b04f8ab4c882","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"8a84035f0b400343","type":"notebook_reader","publish_level":"public","version":126,"publish_version":126,"title":"Map Transform / Observable Plot","update_time":"2021-11-09T15:12:30.088Z","publish_time":"2021-05-04T05:18:51.733Z","likes":6,"comment_count":0,"slug":"plot-map","thumbnail":"664128766139683663b4a54e4d6fb7fe023bb14504da53a6ab8d5845b521646e","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"1a1be1c40c5a1445","type":"notebook_reader","publish_level":"public","version":841,"publish_version":841,"title":"Window Transform / Observable Plot","update_time":"2021-11-10T12:06:08.283Z","publish_time":"2021-11-09T14:58:37.041Z","likes":3,"comment_count":0,"slug":"plot-window","thumbnail":"a675ab3e532f9beef208148886f1b667fac06148ddedad19f33b60ced8c0f612","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"45a379fcfcb14253","github_login":"Fil","login":"fil","name":"Fil","bio":"Vocateur.","home_url":"https://visionscarto.net/","avatar_url":"https://avatars0.githubusercontent.com/u/7001?v=4"}},{"id":"388e64af7cdc956c","type":"notebook_reader","publish_level":"public","version":78,"publish_version":78,"title":"Select Transform / Observable Plot","update_time":"2021-11-09T14:27:46.101Z","publish_time":"2021-05-04T05:18:43.066Z","likes":6,"comment_count":0,"slug":"plot-select","thumbnail":"7901ac621b6ad99134bc3d532a92fb1274836b14b829529c500a38a1cad48c71","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"ffdc4cfe10f27e8c","type":"notebook_reader","publish_level":"public","version":169,"publish_version":169,"title":"Interval Transform / Observable Plot","update_time":"2021-11-09T14:28:53.860Z","publish_time":"2021-11-09T03:41:27.664Z","likes":7,"comment_count":0,"slug":"plot-interval","thumbnail":"e3916c7ce03e0a15565e9a4447e3524f98d28d620c3c38a97461b4a56453aa20","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"074c414ad1d825f5","github_login":"mbostock","login":"mbostock","name":"Mike Bostock","bio":"Building a better computational medium. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","avatar_url":"https://avatars2.githubusercontent.com/u/230541?v=4"}},{"id":"996e6662dd709e40","type":"notebook_reader","publish_level":"public","version":1248,"publish_version":1248,"title":"Legends / Observable Plot","update_time":"2021-12-17T21:47:15.448Z","publish_time":"2021-12-07T20:45:16.171Z","likes":18,"comment_count":14,"slug":"plot-legends","thumbnail":"35db49733fa8e379ee43991bce223d6b426640cc426e8f506c112286778caa11","default_thumbnail":null,"fork_of":false,"collection_count":1,"roles":[],"sharing":null,"notebook_description":"","listing_type":"notebook","owner":{"type":"team","id":"f35c755083683fe5","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4"},"creator":{"type":"individual","id":"45a379fcfcb14253","github_login":"Fil","login":"fil","name":"Fil","bio":"Vocateur.","home_url":"https://visionscarto.net/","avatar_url":"https://avatars0.githubusercontent.com/u/7001?v=4"}}],"parent_collections":[{"id":"152a58e63cb254d5","type":"public","slug":"topics","title":"Topics","description":"Collections related to specific areas of interest, curated by the Observable team. ","update_time":"2020-12-18T01:03:36.841Z","pinned":false,"ordered":true,"custom_thumbnail":null,"default_thumbnail":null,"thumbnail":null,"listing_count":5,"parent_collection_count":0,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","type":"team"}},{"id":"039ee6c342039038","type":"public","slug":"tutorials","title":"Tutorials","description":"Just getting started or want to level up on visualization techniques or the Observable runtime? You’ve come to the right place.","update_time":"2020-11-18T19:20:42.021Z","pinned":false,"ordered":true,"custom_thumbnail":null,"default_thumbnail":null,"thumbnail":null,"listing_count":9,"parent_collection_count":0,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars2.githubusercontent.com/u/30080011?v=4","login":"observablehq","name":"Observable","bio":"Use data to think, together.","home_url":"https://observablehq.com","type":"team"}}]} diff --git a/test/output/documentationLinks.svg b/test/output/documentationLinks.svg new file mode 100644 index 0000000000..7702a2f523 --- /dev/null +++ b/test/output/documentationLinks.svg @@ -0,0 +1,185 @@ + + + + + Observable Plot + + + Marks + + + Scales + + + Transforms + + + Facets + + + Area Mark + + + Bar Mark + + + Cell Mark + + + Dot Mark + + + Image Mark + + + Line Mark + + + Link Mark + + + Rect Mark + + + Rule Mark + + + Text Mark + + + Tick Mark + + + Frame Mark + + + Group Transform + + + Bin Transform + + + Stack Transform + + + Map Transform + + + Window Transform + + + Select Transform + + + Interval Transform + + + Legends + + + + + + 0 + + + + 5 + + + + 10 + + + + 15 + + + + 20 + + + + 25 + + + + 30 + + + + 35 + + + + 40 + likes → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ⚡︎ 296 16 18 6 10 3 9 17 5 11 8 2 2 4 5 1 3 7 12 13 6 3 6 7 18 + + + + \ No newline at end of file diff --git a/test/plots/documentation-links.js b/test/plots/documentation-links.js new file mode 100644 index 0000000000..6d723db347 --- /dev/null +++ b/test/plots/documentation-links.js @@ -0,0 +1,34 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function() { + const data = await d3.json("data/plot-documentation.json").then(d => d.listings); + return Plot.plot({ + marginLeft: 140, + x: { + domain: [0, 41], + clamp: true, + grid: true + }, + y: { + tickFormat: i => data[i].title.replace(" / Observable Plot", "") + }, + marks: [ + Plot.barX(data, { + x: "likes", + y: (d, i) => i, + href: d => `https://observablehq.com/@observablehq/${d.slug}`, + target: "_blank" + }), + Plot.textX(data, { + x: "likes", + y: (d, i) => i, + text: d => `${d.likes > 40 ? "⚡︎" : ""} ${d.likes}`, + textAnchor: "end", + fill: "white", + dx: -3 + }), + Plot.ruleX([0]) + ] + }); +} diff --git a/test/plots/index.js b/test/plots/index.js index c514e1d02b..6dffd7f1bb 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -34,6 +34,7 @@ export {default as d3Survey2015Comfort} from "./d3-survey-2015-comfort.js"; export {default as d3Survey2015Why} from "./d3-survey-2015-why.js"; export {default as diamondsCaratPrice} from "./diamonds-carat-price.js"; export {default as diamondsCaratPriceDots} from "./diamonds-carat-price-dots.js"; +export {default as documentationLinks} from "./documentation-links.js"; export {default as empty} from "./empty.js"; export {default as emptyX} from "./empty-x.js"; export {default as figcaption} from "./figcaption.js";