From 437e15f2819f0cb8f0abb950366d011ae4f56f10 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sun, 16 Jun 2024 09:17:47 -0700 Subject: [PATCH] promote geometry to x and y for tip (#2088) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * promote geometry to x and y for tip * update docs * data provenance * geo mark x & y * move defaults to geo --------- Co-authored-by: Philippe Rivière --- docs/marks/geo.md | 14 +-- src/marks/geo.d.ts | 14 +++ src/marks/geo.js | 13 ++- src/marks/tip.js | 1 + src/memoize.js | 20 ++++- src/transforms/centroid.js | 48 +++++----- test/data/README.md | 8 ++ test/data/london-car-access.csv | 34 +++++++ test/data/london.json | 1 + test/marks/memoize-test.js | 143 +++++++++++++++++------------- test/output/geoText.svg | 86 ++++++++++++++++++ test/output/geoTip.svg | 142 +++++++++++++++++++++++++++++ test/output/geoTip2.svg | 131 +++++++++++++++++++++++++++ test/output/geoTipCentroid.svg | 142 +++++++++++++++++++++++++++++ test/output/geoTipGeoCentroid.svg | 142 +++++++++++++++++++++++++++++ test/output/geoTipXY.svg | 142 +++++++++++++++++++++++++++++ test/plots/geo-tip.ts | 130 +++++++++++++++++++++++++++ test/plots/index.ts | 1 + 18 files changed, 1119 insertions(+), 93 deletions(-) create mode 100644 test/data/london-car-access.csv create mode 100644 test/data/london.json create mode 100644 test/output/geoText.svg create mode 100644 test/output/geoTip.svg create mode 100644 test/output/geoTip2.svg create mode 100644 test/output/geoTipCentroid.svg create mode 100644 test/output/geoTipGeoCentroid.svg create mode 100644 test/output/geoTipXY.svg create mode 100644 test/plots/geo-tip.ts diff --git a/docs/marks/geo.md b/docs/marks/geo.md index 0d35629d6e..56748f0027 100644 --- a/docs/marks/geo.md +++ b/docs/marks/geo.md @@ -49,7 +49,8 @@ Plot.plot({ marks: [ Plot.geo(counties, { fill: (d) => d.properties.unemployment, - title: (d) => `${d.properties.name}\n${d.properties.unemployment}%` + title: (d) => `${d.properties.name} ${d.properties.unemployment}%`, + tip: true }) ] }) @@ -129,17 +130,16 @@ Plot.plot({ ``` ::: -The geo mark doesn’t have **x** and **y** channels; to derive those, for example to add [interactive tips](./tip.md), you can apply a [centroid transform](../transforms/centroid.md) on the geometries. +By default, the geo mark doesn’t have **x** and **y** channels; when you use the [**tip** option](./tip.md), the [centroid transform](../transforms/centroid.md) is implicitly applied on the geometries to compute the tip position by generating **x** and **y** channels. You can alternatively specify these channels explicitly. The centroids are shown below in red. :::plot defer https://observablehq.com/@observablehq/plot-state-centroids ```js Plot.plot({ projection: "albers-usa", marks: [ - Plot.geo(statemesh, {strokeOpacity: 0.2}), + Plot.geo(states, {strokeOpacity: 0.1, tip: true, title: (d) => d.properties.name}), Plot.geo(nation), - Plot.dot(states, Plot.centroid({fill: "red", stroke: "var(--vp-c-bg-alt)"})), - Plot.tip(states, Plot.pointer(Plot.centroid({title: (d) => d.properties.name}))) + Plot.dot(states, Plot.centroid({fill: "red", stroke: "var(--vp-c-bg-alt)"})) ] }) ``` @@ -157,7 +157,7 @@ Plot.plot({ marks: [ Plot.geo(statemesh, {strokeOpacity: 0.2}), Plot.geo(nation), - Plot.geo(walmarts, {fy: (d) => d.properties.date, r: 1.5, fill: "blue"}), + Plot.geo(walmarts, {fy: (d) => d.properties.date, r: 1.5, fill: "blue", tip: true, title: (d) => d.properties.date}), Plot.axisFy({frameAnchor: "top", dy: 30, tickFormat: (d) => `${d.getUTCFullYear()}’s`}) ] }) @@ -176,6 +176,8 @@ The **geometry** channel specifies the geometry (GeoJSON object) to draw; if not In addition to the [standard mark options](../features/marks.md#mark-options), the **r** option controls the size of Point and MultiPoint geometries. It can be specified as either a channel or constant. When **r** is specified as a number, it is interpreted as a constant radius in pixels; otherwise it is interpreted as a channel and the effective radius is controlled by the *r* scale. If the **r** option is not specified it defaults to 3 pixels. Geometries with a nonpositive radius are not drawn. If **r** is a channel, geometries will be sorted by descending radius by default. +The **x** and **y** position channels may also be specified in conjunction with the **tip** option. These are bound to the *x* and *y* scale (or projection), respectively. + ## geo(*data*, *options*) {#geo} ```js diff --git a/src/marks/geo.d.ts b/src/marks/geo.d.ts index d37a23e7fe..035d774c69 100644 --- a/src/marks/geo.d.ts +++ b/src/marks/geo.d.ts @@ -10,6 +10,20 @@ export interface GeoOptions extends MarkOptions { */ geometry?: ChannelValue; + /** + * In conjunction with the tip option, the horizontal position channel + * specifying the tip’s anchor, typically bound to the *x* scale. If not + * specified, defaults to the centroid of the projected geometry. + */ + x?: ChannelValue; + + /** + * In conjunction with the tip option, the vertical position channel + * specifying the tip’s anchor, typically bound to the *y* scale. If not + * specified, defaults to the centroid of the projected geometry. + */ + y?: ChannelValue; + /** * The size of Point and MultiPoint geometries, defaulting to a constant 3 * pixels. If **r** is a number, it is interpreted as a constant radius in diff --git a/src/marks/geo.js b/src/marks/geo.js index e986539377..1c33f0a269 100644 --- a/src/marks/geo.js +++ b/src/marks/geo.js @@ -4,6 +4,7 @@ import {negative, positive} from "../defined.js"; import {Mark} from "../mark.js"; import {identity, maybeNumberChannel} from "../options.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js"; +import {centroid} from "../transforms/centroid.js"; import {withDefaultSort} from "./dot.js"; const defaults = { @@ -22,8 +23,10 @@ export class Geo extends Mark { super( data, { - geometry: {value: options.geometry, scale: "projection"}, - r: {value: vr, scale: "r", filter: positive, optional: true} + x: {value: options.tip ? options.x : null, scale: "x", optional: true}, + y: {value: options.tip ? options.y : null, scale: "y", optional: true}, + r: {value: vr, scale: "r", filter: positive, optional: true}, + geometry: {value: options.geometry, scale: "projection"} }, withDefaultSort(options), defaults @@ -66,7 +69,7 @@ function scaleProjection({x: X, y: Y}) { } } -export function geo(data, {geometry = identity, ...options} = {}) { +export function geo(data, options = {}) { switch (data?.type) { case "FeatureCollection": data = data.features; @@ -85,7 +88,9 @@ export function geo(data, {geometry = identity, ...options} = {}) { data = [data]; break; } - return new Geo(data, {geometry, ...options}); + if (options.tip && options.x === undefined && options.y === undefined) options = centroid(options); + else if (options.geometry === undefined) options = {...options, geometry: identity}; + return new Geo(data, options); } export function sphere({strokeWidth = 1.5, ...options} = {}) { diff --git a/src/marks/tip.js b/src/marks/tip.js index a29d11d619..bfb9d04cb2 100644 --- a/src/marks/tip.js +++ b/src/marks/tip.js @@ -344,6 +344,7 @@ function getSourceChannels(channels, scales) { // Then fallback to all other (non-ignored) channels. for (const key in channels) { if (key in sources || key in format || ignoreChannels.has(key)) continue; + if ((key === "x" || key === "y") && channels.geometry) continue; // ignore x & y on geo const source = getSource(channels, key); if (source) { // Ignore color channels if the values are all literal colors. diff --git a/src/memoize.js b/src/memoize.js index 9395e658be..eceeac7d07 100644 --- a/src/memoize.js +++ b/src/memoize.js @@ -1,7 +1,25 @@ +const unset = Symbol("unset"); + export function memoize1(compute) { + return (compute.length === 1 ? memoize1Arg : memoize1Args)(compute); +} + +function memoize1Arg(compute) { + let cacheValue; + let cacheKey = unset; + return (key) => { + if (!Object.is(cacheKey, key)) { + cacheKey = key; + cacheValue = compute(key); + } + return cacheValue; + }; +} + +function memoize1Args(compute) { let cacheValue, cacheKeys; return (...keys) => { - if (cacheKeys?.length !== keys.length || cacheKeys.some((k, i) => k !== keys[i])) { + if (cacheKeys?.length !== keys.length || cacheKeys.some((k, i) => !Object.is(k, keys[i]))) { cacheKeys = keys; cacheValue = compute(...keys); } diff --git a/src/transforms/centroid.js b/src/transforms/centroid.js index b832775164..a7d745e64f 100644 --- a/src/transforms/centroid.js +++ b/src/transforms/centroid.js @@ -1,32 +1,40 @@ import {geoCentroid as GeoCentroid, geoPath} from "d3"; +import {memoize1} from "../memoize.js"; import {identity, valueof} from "../options.js"; import {initializer} from "./basic.js"; export function centroid({geometry = identity, ...options} = {}) { - // Suppress defaults for x and y since they will be computed by the initializer. - return initializer({...options, x: null, y: null}, (data, facets, channels, scales, dimensions, {projection}) => { - const G = valueof(data, geometry); - const n = G.length; - const X = new Float64Array(n); - const Y = new Float64Array(n); - const path = geoPath(projection); - for (let i = 0; i < n; ++i) [X[i], Y[i]] = path.centroid(G[i]); - return { - data, - facets, - channels: { - x: {value: X, scale: projection == null ? "x" : null, source: null}, - y: {value: Y, scale: projection == null ? "y" : null, source: null} - } - }; - }); + const getG = memoize1((data) => valueof(data, geometry)); + return initializer( + // Suppress defaults for x and y since they will be computed by the initializer. + // Propagate the (memoized) geometry channel in case it’s still needed. + {...options, x: null, y: null, geometry: {transform: getG}}, + (data, facets, channels, scales, dimensions, {projection}) => { + const G = getG(data); + const n = G.length; + const X = new Float64Array(n); + const Y = new Float64Array(n); + const path = geoPath(projection); + for (let i = 0; i < n; ++i) [X[i], Y[i]] = path.centroid(G[i]); + return { + data, + facets, + channels: { + x: {value: X, scale: projection == null ? "x" : null, source: null}, + y: {value: Y, scale: projection == null ? "y" : null, source: null} + } + }; + } + ); } export function geoCentroid({geometry = identity, ...options} = {}) { - let C; + const getG = memoize1((data) => valueof(data, geometry)); + const getC = memoize1((data) => valueof(getG(data), GeoCentroid)); return { ...options, - x: {transform: (data) => Float64Array.from((C = valueof(valueof(data, geometry), GeoCentroid)), ([x]) => x)}, - y: {transform: () => Float64Array.from(C, ([, y]) => y)} + x: {transform: (data) => Float64Array.from(getC(data), ([x]) => x)}, + y: {transform: (data) => Float64Array.from(getC(data), ([, y]) => y)}, + geometry: {transform: getG} }; } diff --git a/test/data/README.md b/test/data/README.md index 303710c6dc..91eb29abbe 100644 --- a/test/data/README.md +++ b/test/data/README.md @@ -115,6 +115,14 @@ CBO https://www.cbo.gov/topics/budget/accuracy-projections https://observablehq.com/@tophtucker/examples-of-bitemporal-charts +## london.json +giCentre, City University of London +https://github.com/gicentre/data + +## london-car-access.csv +Derived by Jo Wood from UK Census data +https://github.com/observablehq/plot/pull/2086 + ## metros.csv The New York Times https://www.nytimes.com/2019/12/02/upshot/wealth-poverty-divide-american-cities.html diff --git a/test/data/london-car-access.csv b/test/data/london-car-access.csv new file mode 100644 index 0000000000..3abc79f002 --- /dev/null +++ b/test/data/london-car-access.csv @@ -0,0 +1,34 @@ +borough,y2001,y2011,y2021 +City of London,0.380,0.306,0.228 +Barking and Dagenham,0.621,0.604,0.652 +Barnet,0.733,0.713,0.701 +Bexley,0.763,0.763,0.776 +Brent,0.627,0.570,0.559 +Bromley,0.770,0.765,0.771 +Camden,0.444,0.389,0.364 +Croydon,0.702,0.665,0.664 +Ealing,0.683,0.647,0.632 +Enfield,0.715,0.675,0.690 +Greenwich,0.592,0.580,0.569 +Hammersmith and Fulham,0.514,0.448,0.425 +Haringey,0.535,0.482,0.473 +Harrow,0.773,0.765,0.753 +Havering,0.767,0.770,0.785 +Hillingdon,0.783,0.773,0.777 +Hounslow,0.714,0.684,0.672 +Islington,0.424,0.353,0.331 +Kensington and Chelsea,0.496,0.440,0.417 +Kingston upon Thames,0.762,0.749,0.743 +Lambeth,0.491,0.422,0.420 +Lewisham,0.572,0.519,0.523 +Merton,0.699,0.674,0.670 +Newham,0.511,0.479,0.483 +Redbridge,0.738,0.721,0.725 +Richmond upon Thames,0.763,0.753,0.746 +Southwark,0.481,0.416,0.397 +Sutton,0.767,0.766,0.772 +Tower Hamlets,0.432,0.370,0.336 +Waltham Forest,0.610,0.581,0.579 +Wandsworth,0.593,0.547,0.521 +Westminster,0.436,0.371,0.338 +Hackney,0.440,0.354,0.351 diff --git a/test/data/london.json b/test/data/london.json new file mode 100644 index 0000000000..7913a83231 --- /dev/null +++ b/test/data/london.json @@ -0,0 +1 @@ +{"type":"Topology","bbox":[-0.5103750689005356,51.28676016315083,0.3340155643740317,51.69187411690989],"transform":{"scale":[0.00008444750807826456,0.00004051544692059764],"translate":[-0.5103750689005356,51.28676016315083]},"objects":{"boroughs":{"type":"GeometryCollection","geometries":[{"type":"Polygon","arcs":[[0,1,2,3,4]],"id":"Kingston upon Thames","properties":{"GSSCode":"E09000021"}},{"type":"Polygon","arcs":[[5,6,7,8,9]],"id":"Croydon","properties":{"GSSCode":"E09000008"}},{"type":"Polygon","arcs":[[-9,10,11,12,13,14,15]],"id":"Bromley","properties":{"GSSCode":"E09000006"}},{"type":"Polygon","arcs":[[16,17,18,19,20,21]],"id":"Hounslow","properties":{"GSSCode":"E09000018"}},{"type":"Polygon","arcs":[[22,23,24,-21,25]],"id":"Ealing","properties":{"GSSCode":"E09000009"}},{"type":"Polygon","arcs":[[26,27,28]],"id":"Havering","properties":{"GSSCode":"E09000016"}},{"type":"Polygon","arcs":[[29,-26,-20,30]],"id":"Hillingdon","properties":{"GSSCode":"E09000017"}},{"type":"Polygon","arcs":[[31,32,33,-23,-30]],"id":"Harrow","properties":{"GSSCode":"E09000015"}},{"type":"Polygon","arcs":[[34,35,-24,-34,36,37,38]],"id":"Brent","properties":{"GSSCode":"E09000005"}},{"type":"Polygon","arcs":[[39,40,41,-37,-33,42]],"id":"Barnet","properties":{"GSSCode":"E09000003"}},{"type":"Polygon","arcs":[[43,44,-11,-8,45,46]],"id":"Lambeth","properties":{"GSSCode":"E09000022"}},{"type":"Polygon","arcs":[[47,48,-12,-45]],"id":"Southwark","properties":{"GSSCode":"E09000028"}},{"type":"Polygon","arcs":[[49,50,51,52,-13,-49]],"id":"Lewisham","properties":{"GSSCode":"E09000023"}},{"type":"MultiPolygon","arcs":[[[53,-51]],[[54,-14,-53,55]]],"id":"Greenwich","properties":{"GSSCode":"E09000011"}},{"type":"Polygon","arcs":[[56,-15,-55]],"id":"Bexley","properties":{"GSSCode":"E09000004"}},{"type":"Polygon","arcs":[[57,58,59,60,-40,61]],"id":"Enfield","properties":{"GSSCode":"E09000010"}},{"type":"Polygon","arcs":[[62,63,64,-58,65,66]],"id":"Waltham Forest","properties":{"GSSCode":"E09000031"}},{"type":"Polygon","arcs":[[67,68,-67,69,-28,70,71]],"id":"Redbridge","properties":{"GSSCode":"E09000026"}},{"type":"Polygon","arcs":[[72,-4,73,-6]],"id":"Sutton","properties":{"GSSCode":"E09000029"}},{"type":"Polygon","arcs":[[74,75,-1,76,-18]],"id":"Richmond upon Thames","properties":{"GSSCode":"E09000027"}},{"type":"Polygon","arcs":[[-74,-3,77,-46,-7]],"id":"Merton","properties":{"GSSCode":"E09000024"}},{"type":"Polygon","arcs":[[78,-47,-78,-2,-76]],"id":"Wandsworth","properties":{"GSSCode":"E09000032"}},{"type":"Polygon","arcs":[[-22,-25,-36,79,80]],"id":"Hammersmith and Fulham","properties":{"GSSCode":"E09000013"}},{"type":"Polygon","arcs":[[-80,-35,81,82]],"id":"Kensington and Chelsea","properties":{"GSSCode":"E09000020"}},{"type":"Polygon","arcs":[[-82,-39,83,84,85]],"id":"Westminster","properties":{"GSSCode":"E09000033"}},{"type":"Polygon","arcs":[[86,87,-84,-38,-42,88]],"id":"Camden","properties":{"GSSCode":"E09000007"}},{"type":"Polygon","arcs":[[89,90,91,92,93,94]],"id":"Tower Hamlets","properties":{"GSSCode":"E09000030"}},{"type":"Polygon","arcs":[[95,96,97,-87]],"id":"Islington","properties":{"GSSCode":"E09000019"}},{"type":"Polygon","arcs":[[-64,98,-93,99,-97,100]],"id":"Hackney","properties":{"GSSCode":"E09000012"}},{"type":"Polygon","arcs":[[-96,-89,-41,-61,59,-59,-65,-101]],"id":"Haringey","properties":{"GSSCode":"E09000014"}},{"type":"MultiPolygon","arcs":[[[101,102]],[[-94,-99,-63,-69,103,104]]],"id":"Newham","properties":{"GSSCode":"E09000025"}},{"type":"MultiPolygon","arcs":[[[-104,-68,105,-103,106]],[[-71,-27,107]]],"id":"Barking and Dagenham","properties":{"GSSCode":"E09000002"}},{"type":"Polygon","arcs":[[-85,-88,-98,-100,-92,90,-90,108]],"id":"City of London","properties":{"GSSCode":"E09000001"}}]}},"arcs":[[[2281,2639],[96,156],[14,361],[29,169],[-30,91],[-70,76],[45,98],[50,-51],[58,10],[96,-100],[33,62],[48,-218],[73,41],[112,190],[200,191]],[[3035,3715],[35,-116]],[[3070,3599],[-37,-83],[22,-72],[30,-242],[61,-299],[-32,-168],[68,-91],[23,-113]],[[3205,2531],[12,-79],[-75,-150]],[[3142,2302],[-101,-31],[-88,20],[-44,-98],[-86,-74],[-193,-260],[38,-21],[-72,-115],[-19,-96],[-87,-128],[-47,-110],[-19,-188],[-161,-188],[-107,-33],[-29,61],[31,373],[-28,108],[29,92],[69,100],[47,180],[3,168],[43,107],[67,20],[14,88],[-40,177],[-81,185]],[[4190,858],[130,49],[13,73],[-58,57],[63,47],[-42,200],[35,82],[202,132],[9,-64],[117,22],[-58,329],[6,140],[-80,389],[-17,165],[-58,91]],[[4452,2570],[117,161],[-79,163],[-15,108],[56,97]],[[4531,3099],[33,24],[65,141],[80,105],[223,-11],[88,-87],[94,14]],[[5114,3285],[-23,-106],[91,-71],[-8,-76],[78,-169],[88,-86],[76,54],[28,-47],[-33,-108],[132,-145],[68,-21],[-16,-129],[11,-153],[80,5],[40,53],[38,-177],[6,-128],[88,-201],[86,-80],[38,-80],[89,-574]],[[6071,1046],[-131,118],[-66,-102],[-74,182],[-64,35],[-81,-28],[-60,31],[-49,-121],[-98,-29],[29,-182],[-41,-69],[-152,-94],[-70,6],[-12,58],[-93,-36],[-70,-97],[34,-128],[-81,-85],[-28,-142],[-38,-52],[-198,-175],[-42,4],[-37,-132],[-77,-8],[-69,144],[-12,71],[-74,131],[-39,-30],[-174,42],[-28,82],[40,142],[-90,229],[64,47]],[[5114,3285],[2,19]],[[5116,3304],[52,136]],[[5168,3440],[131,-22],[72,-56],[155,-2],[74,52],[86,17],[110,-117],[-19,-84],[54,-61],[88,-38],[115,58],[30,66],[46,-3],[29,114],[72,77],[82,16],[52,53],[127,-163],[30,56],[-12,215],[-47,-41],[-99,99],[-7,63],[48,13],[6,72]],[[6391,3824],[12,65],[111,-82],[224,-405],[56,-8],[142,191]],[[6936,3585],[86,-44],[88,-170],[201,-224],[77,-29],[184,45],[235,-159]],[[7807,3004],[64,-87],[56,-228],[39,-79],[-173,7],[25,-47],[1,-269],[39,-48],[-24,-145],[-73,-230],[10,-114],[-39,-185],[-67,-162],[-140,42],[-77,-41],[-23,-68],[58,-190],[-87,-157],[-112,-1],[-126,-174],[-108,-106],[-26,-256],[42,-136],[57,-81],[-70,-97],[-317,-88],[-29,75],[-65,39],[-97,-32],[23,146],[-22,77],[-113,143],[-98,-139],[-65,-251],[-49,2],[-78,470],[23,131],[-45,-12],[-50,333]],[[3148,4984],[-45,-44],[-35,-106],[0,-108],[-36,-124],[-69,-47],[-108,57],[-77,206],[-69,106],[-132,30],[-79,-61],[-16,-78],[-73,-138],[-163,-127],[-12,-141]],[[2234,4409],[-59,-90],[13,-93],[-69,-17],[-66,-66],[-37,114],[-93,-60],[-100,40],[-197,-23],[-127,-197],[-47,-2],[18,-67],[99,-106],[79,21],[52,-40],[-131,-120],[21,-31],[-85,-84],[-57,-105],[47,-128],[-16,-48],[-70,39]],[[1409,3346],[-98,30],[-68,-26],[-100,187],[-87,52],[-99,-83],[-124,45],[3,98],[-94,10],[17,123],[-121,-46],[-14,274],[-45,-6],[33,181]],[[612,4185],[174,-75],[30,87],[57,22],[36,100],[133,117],[133,84],[-56,102],[-11,169],[50,191],[20,227],[47,47]],[[1225,5256],[235,-124],[109,47],[71,-151],[103,133],[148,80],[101,-76],[144,-23],[78,20],[50,-41],[40,40],[46,-96],[77,63],[-5,41],[98,-32],[-37,110],[79,55],[98,-20],[59,51],[119,-207],[181,12],[28,160]],[[3047,5298],[45,-110],[57,24],[-1,-228]],[[1570,6620],[23,-47],[158,73],[1,31],[176,59],[59,-54],[83,-22]],[[2070,6660],[90,-32],[180,-148],[66,-100],[10,-282],[43,-45],[82,82],[83,19],[75,64],[47,-82],[-9,-57],[-57,11],[-63,-45],[104,-79],[166,50],[104,119],[136,-63]],[[3127,6072],[-74,-190],[44,-281],[26,-60],[11,-164],[-111,-8],[24,-71]],[[1225,5256],[38,84],[126,106],[34,156],[90,104],[79,192],[-3,76],[-441,144],[-73,100],[168,93],[120,2],[-41,132],[159,81],[89,94]],[[7923,5564],[80,401],[63,103],[50,145],[50,45],[10,85],[68,86],[52,134],[-69,123],[-11,172],[-119,12],[-136,-86],[-144,199],[-27,274],[41,73],[2,289],[-35,87]],[[7798,7706],[16,97],[-25,237],[-109,272]],[[7680,8312],[202,-4],[161,-48],[137,75],[170,31],[65,-19],[125,44],[136,87],[21,37],[182,-213],[151,-132],[66,-91],[77,-154],[-74,-33],[-33,-130],[173,-41],[-6,-101],[32,-191],[119,-227],[97,-352],[269,38],[26,-196],[85,-127],[33,8],[74,-176],[31,-134],[-148,0],[-247,-43],[-35,-85],[-303,-52],[-80,-26],[0,-180],[-20,-173],[-117,1],[-35,269],[-95,-109],[-25,-78],[38,-71],[-80,24],[47,-138],[9,-142],[-180,-33],[39,-179],[-55,-45],[-115,-23],[-72,-170],[-170,-43],[-90,-2],[-34,68],[-27,217],[-46,144],[-176,163],[-59,7]],[[1259,8057],[23,-156],[-3,-122],[69,-166],[12,-124],[40,-42],[7,-171],[163,-656]],[[612,4185],[-177,57],[-37,65],[-122,4],[-81,32],[-89,115],[-105,3],[60,163],[20,158],[57,140],[1,74],[118,168],[-13,74],[43,48],[35,155],[-77,104],[-32,140],[20,240],[20,59],[-77,225],[105,289],[110,129],[-2,124],[-44,17],[-42,146],[9,196],[-49,41],[-17,130],[-60,68],[-58,187],[-16,198],[54,61],[-34,160],[45,246],[-57,78],[31,111],[-32,50],[47,75],[57,-24],[35,-89],[141,-130],[89,-125],[139,-111],[98,70],[43,111],[88,5],[133,-69],[125,-18],[59,-62],[86,-16]],[[1259,8057],[203,59],[240,130],[82,109],[124,40],[111,95],[19,-31],[88,133],[139,91],[28,49],[145,-104]],[[2438,8628],[6,-120],[436,-767]],[[2880,7741],[-111,-96],[-164,-72],[76,-83],[18,-128],[-244,55],[-119,-74],[-84,-95],[-77,-41],[54,-225],[-90,-63],[18,-113],[-87,-146]],[[3486,5952],[-148,60]],[[3338,6012],[-70,57],[-108,-21],[-33,24]],[[2880,7741],[224,-396],[-73,-70],[36,-206],[-21,-16],[73,-96],[3,70],[62,31],[95,-18],[136,-223],[100,-192]],[[3515,6625],[261,-466]],[[3776,6159],[-60,-213],[-76,129],[-145,-37],[-9,-86]],[[3887,9425],[-45,-143],[369,-180],[36,-251],[45,-81],[33,14],[44,-103],[145,-153],[-74,-261],[-108,-153],[68,-131]],[[4400,7983],[-55,-33],[16,-137],[-28,-79],[-82,-66],[-23,129],[-41,56],[-58,-179],[50,-274],[-98,-83],[-66,-266]],[[4015,7051],[-37,-79],[-43,30],[-140,-136],[-10,-77],[-92,-76],[-20,-63],[-67,-22],[-49,36],[-42,-39]],[[2438,8628],[99,-22],[260,86],[15,78],[114,68],[70,-74],[75,188],[14,127],[137,90],[111,43],[29,-70],[73,82],[147,59],[-39,101],[38,-9],[54,87],[101,-45],[-21,-69],[66,-40],[39,99],[67,18]],[[4523,4894],[49,67],[37,177],[21,220],[41,87],[83,27]],[[4754,5472],[35,-159],[-58,-163],[71,-47],[20,-58],[-63,-131],[53,-122],[-50,-16],[98,-84],[-8,-64],[55,-108],[35,52],[35,-143],[-7,-72],[-61,-145],[-61,-80],[2,-94],[56,-89],[65,-254],[32,-182],[113,-209]],[[4531,3099],[-75,-33],[-102,58],[-64,-11],[91,157]],[[4381,3270],[30,46],[-6,233],[37,-5],[-52,123],[44,165],[-91,-4],[-23,87],[38,139],[-64,33],[-36,264],[2,113],[177,126],[86,304]],[[4754,5472],[158,-3],[172,-60],[204,-128],[77,3],[81,54],[72,96],[104,-1],[38,-66],[0,-275]],[[5660,5092],[-97,-7],[-23,-63],[-112,-27],[-28,-260],[20,-15],[16,-262],[59,-73],[2,-184],[54,-49],[-54,-126],[-92,-29],[-43,40],[-73,-143],[2,-174],[-29,-76],[-72,-47],[-36,-91],[14,-66]],[[5660,5092],[89,-186]],[[5749,4906],[-18,-99],[39,-47],[71,20]],[[5841,4780],[-67,-125]],[[5774,4655],[43,-134],[71,-25],[-24,70],[83,25],[97,-23],[177,31],[16,-101],[-86,-77],[29,-229],[71,-38],[52,-151],[-5,-153],[93,-26]],[[5749,4906],[72,-50],[20,-76]],[[7467,5546],[12,-497],[12,-234],[23,-124],[-68,52],[-76,-144],[-45,75],[-73,-41],[-45,25],[-159,-167],[-30,-51],[7,-168],[-50,-14],[83,-65],[27,-248],[-7,-83],[-77,-70],[-65,-207]],[[5774,4655],[91,155],[-32,40],[125,13],[87,76],[30,122],[-35,85],[-12,200],[77,23],[116,-199],[105,-56],[194,37],[263,-24],[168,44],[68,67],[114,247],[176,10],[158,51]],[[7467,5546],[57,39],[86,-18],[147,-120],[183,-46],[78,-55],[63,-169],[6,-191],[65,-130],[73,-65],[119,-39],[211,82],[66,-54],[-17,-75],[-59,-52],[-37,-109],[34,-59],[-8,-144],[-93,-107],[4,-107],[-116,-56],[-64,-74],[-39,-100],[-99,-65],[-38,25],[-72,-197],[-32,-151],[-96,48],[-28,-185],[-28,-73],[-23,-197],[54,-29],[-57,-69]],[[5898,8872],[-37,-270],[-117,-319],[-88,-149],[-34,-106],[19,-64],[-88,-94]],[[5553,7870],[-168,81],[-136,1]],[[5249,7952],[0,0]],[[5249,7952],[-222,-12],[-115,20],[-135,-24],[-196,10],[-136,62],[-45,-25]],[[3887,9425],[76,112],[38,-2],[105,230],[1,141],[162,-64],[180,81],[153,-6],[112,61],[96,19],[111,-42],[106,2],[81,-31],[150,-125],[63,-24],[113,17],[282,-33],[196,-34],[9,-110],[-14,-308],[31,-91],[-35,-189],[-5,-157]],[[6285,6652],[-41,-76],[-28,44],[-60,-92],[-76,21],[-139,-54],[-23,67],[-78,-26]],[[5840,6536],[-15,89],[-129,152],[-47,-18],[-166,54],[7,41],[-136,194],[-35,135]],[[5319,7183],[-13,43],[120,209],[-16,47],[34,164],[109,224]],[[5898,8872],[58,-1],[-11,-62],[93,-3],[16,-43],[204,-38],[54,21],[35,-155],[-45,-148]],[[6302,8443],[-35,-138],[-52,-107],[-69,4],[90,-55],[61,-141],[-100,-298],[-10,-170],[15,-207],[48,-156],[-13,-112],[32,-146],[-54,-35],[-3,-105],[73,-125]],[[6865,6352],[-12,7]],[[6853,6359],[-6,214],[-50,105],[-41,-22],[-68,80],[-48,107],[-100,-63],[50,-163],[-132,9],[-114,-28],[-59,54]],[[6302,8443],[86,-92],[140,-233],[146,68],[-53,-119],[168,-165],[117,-55],[71,57],[98,-62],[65,42],[26,113],[-30,69],[69,46],[25,-67],[112,31],[84,73],[22,-38],[66,52],[59,98],[107,51]],[[7798,7706],[-19,11],[-201,-231],[17,-63],[-55,-19],[17,-126],[56,-8],[-40,-123],[29,-111],[-27,-132],[-122,-84],[-22,-153],[-72,-108],[-208,-164],[-15,68],[-166,-107],[-101,-5]],[[6869,6351],[-4,1]],[[4190,858],[-91,112],[13,104],[-116,-6],[-15,79],[-157,184],[-117,72],[-67,-93],[-119,-106],[-13,-128],[-81,-12],[-105,164],[149,170],[-56,201],[-18,170],[-40,103],[-121,106],[-99,-1],[11,89],[-6,236]],[[3205,2531],[60,30],[11,-73],[71,-89],[114,-94],[98,200],[110,-83],[78,74],[66,-61],[113,116],[47,82],[100,-92],[13,-43],[248,67],[29,-33],[89,38]],[[2234,4409],[85,-133],[94,-82],[33,-87],[-65,-116],[-106,-50],[-101,-16],[-58,-164],[64,-184],[-47,235],[96,100],[100,26],[94,62],[35,61],[-21,118],[-174,199],[7,148],[130,107],[120,220],[83,71],[68,-4],[93,-126],[44,-156],[64,-85],[90,-23],[63,30],[59,102],[19,205],[86,109],[122,-19],[32,-61],[21,-239],[34,-97]],[[3398,4560],[-114,15],[-3,-201],[-115,30],[-117,1],[-61,-122],[-13,-132],[140,-142],[67,-149],[-147,-145]],[[2281,2639],[-118,-47],[-160,286],[-31,15],[-189,199],[-79,-22],[-79,-63],[-75,-19],[-51,57],[-70,14],[-7,90],[49,-2],[-18,125],[-44,74]],[[3070,3599],[234,137],[84,18],[101,-27],[122,25],[182,66],[-12,-207],[63,3],[14,-137],[31,-72],[241,-120],[20,69],[183,-121],[48,37]],[[3398,4560],[119,-140],[159,-76],[136,24],[53,41],[65,133],[45,190],[81,64],[220,64],[141,-23],[106,57]],[[3338,6012],[21,-206],[-19,-22],[68,-135],[39,-150],[48,-47],[-33,-37],[46,-133],[132,-184],[60,-135],[41,-33],[125,-224]],[[3866,4706],[53,5],[-40,-199],[-42,-77],[-87,-40],[-103,12],[-160,94],[-72,117],[-29,237],[-58,138],[-45,31],[-135,-40]],[[3486,5952],[112,-45],[70,-131],[-37,-3],[54,-151],[49,12],[34,-127],[50,7],[44,-207],[46,-8],[9,-91],[167,23],[83,88],[43,-254],[-12,-58],[69,-102]],[[4267,4905],[-305,-95],[-42,-90],[-54,-14]],[[3776,6159],[32,-44],[102,125],[126,-24],[54,-70],[145,43],[66,-308],[51,-28],[69,-124],[73,-44],[39,-110],[55,62],[134,5]],[[4722,5642],[0,-113]],[[4722,5529],[-99,-49],[-30,-74],[-33,-345],[-75,-141],[-98,-32],[-120,17]],[[4357,6969],[16,-212],[46,-127],[71,-92],[51,-115],[51,-231],[0,-170],[78,-22],[43,-152],[63,-37],[20,-90]],[[4796,5721],[-100,-7],[26,-72]],[[4015,7051],[47,13],[201,-34],[94,-61]],[[5104,5457],[7,24]],[[5111,5481],[0,0]],[[5111,5481],[71,36],[-14,103],[-65,108],[11,66]],[[5114,5794],[28,16],[-13,127],[36,70],[93,27],[79,71],[116,57],[62,-13],[139,216],[46,-58],[147,26]],[[5847,6333],[-55,-153],[52,-148],[70,-43],[41,-79],[-4,-79]],[[5951,5831],[-16,-78],[58,-85],[19,34],[75,-80],[-27,-87],[87,-91],[-60,26],[-64,-24],[-57,-84],[-15,-79],[54,-214],[-5,-78],[-58,-60],[-93,-10],[-59,33],[-76,170],[0,264],[-26,59],[-127,59],[-84,-29],[-100,-121],[-77,-16],[-196,117]],[[4357,6969],[112,98],[159,60],[53,-122],[127,-143]],[[4808,6862],[68,-97],[82,-15],[15,-150],[127,-58],[34,-141],[-83,12],[-33,-230],[-123,-106],[19,-93],[123,-74],[-2,-145]],[[5035,5765],[-151,63],[3,-53],[-91,-54]],[[5840,6536],[-21,-27],[28,-176]],[[5114,5794],[-79,-29]],[[4808,6862],[98,145],[-20,73],[200,49],[55,-16],[178,70]],[[6938,5999],[-1,1]],[[6937,6000],[1,-1]],[[6853,6359],[-14,-125],[71,-246]],[[6910,5988],[63,-13],[80,-80],[80,1],[46,-252],[-66,-18],[-82,-78],[-115,-275],[-158,-50],[-192,39],[-210,-33],[-102,48],[-133,238],[9,102],[-115,95],[-29,-30],[-44,76],[9,73]],[[6865,6352],[-9,-83],[82,-270]],[[6937,6000],[-27,-12]],[[7923,5564],[-74,10],[-105,63],[-90,84],[-105,22],[-94,-32],[-148,-104],[-87,22],[-45,53],[-1,88],[-44,153],[-73,-16],[-107,92],[-90,300],[9,52]],[[5104,5457],[-241,72],[-141,0]]]} diff --git a/test/marks/memoize-test.js b/test/marks/memoize-test.js index c41f660642..4ef82acc70 100644 --- a/test/marks/memoize-test.js +++ b/test/marks/memoize-test.js @@ -1,71 +1,90 @@ import {memoize1} from "../../src/memoize.js"; import assert from "assert"; -it("memoize1(compute) returns the cached value with repeated calls", () => { - let index = 0; - const m = memoize1(() => ++index); - assert.strictEqual(m(), 1); - assert.strictEqual(m(), 1); - assert.strictEqual(m(), 1); - assert.strictEqual(m(0), 2); - assert.strictEqual(m(0), 2); - assert.strictEqual(m(0), 2); -}); +describe("memoize1(compute)", () => { + it("returns the cached value with repeated calls", () => { + let index = 0; + const m = memoize1(() => ++index); + assert.strictEqual(m(), 1); + assert.strictEqual(m(), 1); + assert.strictEqual(m(), 1); + assert.strictEqual(m(0), 2); + assert.strictEqual(m(0), 2); + assert.strictEqual(m(0), 2); + }); -it("memoize1(compute) computes a new value with a different argument value", () => { - let index = 0; - const m = memoize1(() => ++index); - assert.strictEqual(m(), 1); - assert.strictEqual(m(0), 2); - assert.strictEqual(m(undefined), 3); - assert.strictEqual(m(null), 4); - assert.strictEqual(m(0), 5); - assert.strictEqual(m(0, 1), 6); - assert.strictEqual(m(0, 2), 7); -}); + it("works with a single-argument function", () => { + let index = 0; + const m = memoize1((arg) => [arg, ++index]); + assert.deepStrictEqual(m(), [undefined, 1]); + assert.deepStrictEqual(m(), [undefined, 1]); + assert.deepStrictEqual(m(undefined), [undefined, 1]); + assert.deepStrictEqual(m(undefined), [undefined, 1]); + assert.deepStrictEqual(m(null), [null, 2]); + assert.deepStrictEqual(m(null), [null, 2]); + assert.deepStrictEqual(m(undefined), [undefined, 3]); + assert.deepStrictEqual(m(undefined), [undefined, 3]); + assert.deepStrictEqual(m(NaN), [NaN, 4]); + assert.deepStrictEqual(m(NaN), [NaN, 4]); + }); -it("memoize1(compute) computes a new value with different number of arguments", () => { - let index = 0; - const m = memoize1(() => ++index); - assert.strictEqual(m(0), 1); - assert.strictEqual(m(0, 0), 2); - assert.strictEqual(m(0, 0, 0), 3); - assert.strictEqual(m(0, 0), 4); - assert.strictEqual(m(0), 5); -}); + it("computes a new value with a different argument value", () => { + let index = 0; + const m = memoize1(() => ++index); + assert.strictEqual(m(), 1); + assert.strictEqual(m(0), 2); + assert.strictEqual(m(undefined), 3); + assert.strictEqual(m(null), 4); + assert.strictEqual(m(0), 5); + assert.strictEqual(m(0, 1), 6); + assert.strictEqual(m(0, 2), 7); + }); -it("memoize1(compute) only caches a single value", () => { - let index = 0; - const m = memoize1(() => ++index); - assert.strictEqual(m(0), 1); - assert.strictEqual(m(1), 2); - assert.strictEqual(m(1), 2); - assert.strictEqual(m(0), 3); - assert.strictEqual(m(0), 3); - assert.strictEqual(m(1), 4); - assert.strictEqual(m(1), 4); -}); + it("computes a new value with different number of arguments", () => { + let index = 0; + const m = memoize1(() => ++index); + assert.strictEqual(m(0), 1); + assert.strictEqual(m(0, 0), 2); + assert.strictEqual(m(0, 0, 0), 3); + assert.strictEqual(m(0, 0), 4); + assert.strictEqual(m(0), 5); + }); -it("memoize1(compute) determines equality with strict equals", () => { - let index = 0; - const m = memoize1(() => ++index); - assert.strictEqual(m([0]), 1); - assert.strictEqual(m([1]), 2); - assert.strictEqual(m([1]), 3); - assert.strictEqual(m([0]), 4); - assert.strictEqual(m([0]), 5); - assert.strictEqual(m([1]), 6); - assert.strictEqual(m([1]), 7); -}); + it("only caches a single value", () => { + let index = 0; + const m = memoize1(() => ++index); + assert.strictEqual(m(0), 1); + assert.strictEqual(m(1), 2); + assert.strictEqual(m(1), 2); + assert.strictEqual(m(0), 3); + assert.strictEqual(m(0), 3); + assert.strictEqual(m(1), 4); + assert.strictEqual(m(1), 4); + }); + + it("determines equality with strict equals", () => { + let index = 0; + const m = memoize1(() => ++index); + assert.strictEqual(m([0]), 1); + assert.strictEqual(m([1]), 2); + assert.strictEqual(m([1]), 3); + assert.strictEqual(m([0]), 4); + assert.strictEqual(m([0]), 5); + assert.strictEqual(m([1]), 6); + assert.strictEqual(m([1]), 7); + }); -it("memoize1(compute) passes the specified arguments to compute", () => { - let index = 0; - const m = memoize1((...args) => [...args, ++index]); - assert.deepStrictEqual(m(0), [0, 1]); - assert.deepStrictEqual(m(1), [1, 2]); - assert.deepStrictEqual(m(1), [1, 2]); - assert.deepStrictEqual(m(0, 1), [0, 1, 3]); - assert.deepStrictEqual(m(0, 1), [0, 1, 3]); - assert.deepStrictEqual(m(1, 0), [1, 0, 4]); - assert.deepStrictEqual(m(1, 0), [1, 0, 4]); + it("passes the specified arguments to compute", () => { + let index = 0; + const m = memoize1((...args) => [...args, ++index]); + assert.deepStrictEqual(m(0), [0, 1]); + assert.deepStrictEqual(m(1), [1, 2]); + assert.deepStrictEqual(m(1), [1, 2]); + assert.deepStrictEqual(m(0, 1), [0, 1, 3]); + assert.deepStrictEqual(m(0, 1), [0, 1, 3]); + assert.deepStrictEqual(m(1, 0), [1, 0, 4]); + assert.deepStrictEqual(m(1, 0), [1, 0, 4]); + assert.deepStrictEqual(m(1, NaN), [1, NaN, 5]); + assert.deepStrictEqual(m(1, NaN), [1, NaN, 5]); + }); }); diff --git a/test/output/geoText.svg b/test/output/geoText.svg new file mode 100644 index 0000000000..bb2ecf855a --- /dev/null +++ b/test/output/geoText.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Kingston upon Thames + Croydon + Bromley + Hounslow + Ealing + Havering + Hillingdon + Harrow + Brent + Barnet + Lambeth + Southwark + Lewisham + Greenwich + Bexley + Enfield + Waltham Forest + Redbridge + Sutton + Richmond upon Thames + Merton + Wandsworth + Hammersmith and Fulham + Kensington and Chelsea + Westminster + Camden + Tower Hamlets + Islington + Hackney + Haringey + Newham + Barking and Dagenham + City of London + + \ No newline at end of file diff --git a/test/output/geoTip.svg b/test/output/geoTip.svg new file mode 100644 index 0000000000..1bbaa91313 --- /dev/null +++ b/test/output/geoTip.svg @@ -0,0 +1,142 @@ + + + + + 2001 + + + 2011 + + + 2021 + + + + year + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/geoTip2.svg b/test/output/geoTip2.svg new file mode 100644 index 0000000000..b3fa342a4b --- /dev/null +++ b/test/output/geoTip2.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/geoTipCentroid.svg b/test/output/geoTipCentroid.svg new file mode 100644 index 0000000000..1bbaa91313 --- /dev/null +++ b/test/output/geoTipCentroid.svg @@ -0,0 +1,142 @@ + + + + + 2001 + + + 2011 + + + 2021 + + + + year + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/geoTipGeoCentroid.svg b/test/output/geoTipGeoCentroid.svg new file mode 100644 index 0000000000..1bbaa91313 --- /dev/null +++ b/test/output/geoTipGeoCentroid.svg @@ -0,0 +1,142 @@ + + + + + 2001 + + + 2011 + + + 2021 + + + + year + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/geoTipXY.svg b/test/output/geoTipXY.svg new file mode 100644 index 0000000000..1bbaa91313 --- /dev/null +++ b/test/output/geoTipXY.svg @@ -0,0 +1,142 @@ + + + + + 2001 + + + 2011 + + + 2021 + + + + year + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/geo-tip.ts b/test/plots/geo-tip.ts new file mode 100644 index 0000000000..3079418444 --- /dev/null +++ b/test/plots/geo-tip.ts @@ -0,0 +1,130 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; +import {feature} from "topojson-client"; + +export async function geoText() { + const london = feature(await d3.json("data/london.json"), "boroughs"); + return Plot.plot({ + projection: {type: "transverse-mercator", rotate: [2, 0, 0], domain: london}, + marks: [ + Plot.geo(london), + Plot.text(london.features, Plot.centroid({text: "id", stroke: "var(--plot-background)", fill: "currentColor"})) + ] + }); +} + +/** The geo mark with the tip option. */ +export async function geoTip() { + const [london, boroughs] = await getLondonBoroughs(); + const access = await getLondonAccess(); + return Plot.plot({ + width: 900, + projection: {type: "transverse-mercator", rotate: [2, 0, 0], domain: london}, + color: {scheme: "RdYlBu", pivot: 0.5}, + marks: [ + Plot.geo(access, { + fx: "year", + geometry: (d) => boroughs.get(d.borough), + fill: "access", + stroke: "var(--plot-background)", + strokeWidth: 0.75, + channels: {borough: "borough"}, + tip: true + }) + ] + }); +} + +/** The geo mark with the tip option and the centroid transform. */ +export async function geoTipCentroid() { + const [london, boroughs] = await getLondonBoroughs(); + const access = await getLondonAccess(); + return Plot.plot({ + width: 900, + projection: {type: "transverse-mercator", rotate: [2, 0, 0], domain: london}, + color: {scheme: "RdYlBu", pivot: 0.5}, + marks: [ + Plot.geo( + access, + Plot.centroid({ + fx: "year", + geometry: (d) => boroughs.get(d.borough), + fill: "access", + stroke: "var(--plot-background)", + strokeWidth: 0.75, + channels: {borough: "borough"}, + tip: true + }) + ) + ] + }); +} + +/** The geo mark with the tip option and the geoCentroid transform. */ +export async function geoTipGeoCentroid() { + const [london, boroughs] = await getLondonBoroughs(); + const access = await getLondonAccess(); + return Plot.plot({ + width: 900, + projection: {type: "transverse-mercator", rotate: [2, 0, 0], domain: london}, + color: {scheme: "RdYlBu", pivot: 0.5}, + marks: [ + Plot.geo( + access, + Plot.geoCentroid({ + fx: "year", + geometry: (d) => boroughs.get(d.borough), + fill: "access", + stroke: "var(--plot-background)", + strokeWidth: 0.75, + channels: {borough: "borough"}, + tip: true + }) + ) + ] + }); +} + +function getFirstPoint(feature) { + return feature.geometry.type === "Polygon" + ? feature.geometry.coordinates[0][0] + : feature.geometry.coordinates[0][0][0]; +} + +/** The geo mark with the tip option and x and y channels. */ +export async function geoTipXY() { + const [london, boroughs] = await getLondonBoroughs(); + const access = await getLondonAccess(); + return Plot.plot({ + width: 900, + projection: {type: "transverse-mercator", rotate: [2, 0, 0], domain: london}, + color: {scheme: "RdYlBu", pivot: 0.5}, + marks: [ + Plot.geo(access, { + x: (d) => getFirstPoint(boroughs.get(d.borough))[0], + y: (d) => getFirstPoint(boroughs.get(d.borough))[1], + fx: "year", + geometry: (d) => boroughs.get(d.borough), + fill: "access", + stroke: "var(--plot-background)", + strokeWidth: 0.75, + channels: {borough: "borough"}, + tip: true + }) + ] + }); +} + +async function getLondonBoroughs() { + const london = feature(await d3.json("data/london.json"), "boroughs"); + const boroughs = new Map(london.features.map((d) => [d.id, d])); + return [london, boroughs]; +} + +async function getLondonAccess() { + return (await d3.csv("data/london-car-access.csv", d3.autoType)).flatMap(({borough, y2001, y2011, y2021}) => [ + {borough, year: "2001", access: y2001}, + {borough, year: "2011", access: y2011}, + {borough, year: "2021", access: y2021} + ]); +} diff --git a/test/plots/index.ts b/test/plots/index.ts index 907109e590..f1b253323e 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -103,6 +103,7 @@ export * from "./fruit-sales.js"; export * from "./function-contour.js"; export * from "./geo-line.js"; export * from "./geo-link.js"; +export * from "./geo-tip.js"; export * from "./gistemp-anomaly-moving.js"; export * from "./gistemp-anomaly-transform.js"; export * from "./gistemp-anomaly.js";