|
1 | 1 | import * as Plot from "@observablehq/plot"; |
2 | 2 | import * as d3 from "d3"; |
3 | 3 | import assert from "../assert.js"; |
4 | | -import {it} from "vitest"; |
| 4 | +import {describe, it} from "vitest"; |
5 | 5 |
|
6 | 6 | // TODO Expose as d3.schemeObservable10, or Plot.scheme("observable10")? |
7 | 7 | const schemeObservable10 = [ |
@@ -2309,3 +2309,113 @@ function scaleApply(x, pairs) { |
2309 | 2309 | assert.strictEqual(+x.invert(output).toFixed(10), input); |
2310 | 2310 | } |
2311 | 2311 | } |
| 2312 | + |
| 2313 | +describe("plot(…).scale('projection')", () => { |
| 2314 | + it("returns undefined when no projection is used", () => { |
| 2315 | + const plot = Plot.frame().plot(); |
| 2316 | + assert.strictEqual(plot.scale("projection"), undefined); |
| 2317 | + }); |
| 2318 | + |
| 2319 | + it("returns the projection for a named projection", () => { |
| 2320 | + const plot = Plot.plot({projection: "mercator", marks: [Plot.graticule()]}); |
| 2321 | + const projection = plot.scale("projection"); |
| 2322 | + assert.strictEqual(d3.geoPath(projection)({type: "Point", coordinates: [-1.55, 47.22]}), "M316.749,224.179m0,4.5a4.5,4.5 0 1,1 0,-9a4.5,4.5 0 1,1 0,9z"); // prettier-ignore |
| 2323 | + assert.allCloseTo(projection.apply([-1.55, 47.22]), [316.74875, 224.179291]); |
| 2324 | + assert.allCloseTo(projection.invert([316.74875, 224.179291]), [-1.55, 47.22]); |
| 2325 | + }); |
| 2326 | + |
| 2327 | + it("returns the projection for a projection implementation", () => { |
| 2328 | + const plot = Plot.plot({projection: d3.geoMercator(), marks: [Plot.graticule()]}); |
| 2329 | + const projection = plot.scale("projection"); |
| 2330 | + assert.strictEqual(d3.geoPath(projection)({type: "Point", coordinates: [-1.55, 47.22]}), "M475.862,106.646m0,4.5a4.5,4.5 0 1,1 0,-9a4.5,4.5 0 1,1 0,9z"); // prettier-ignore |
| 2331 | + assert.allCloseTo(projection.apply([-1.55, 47.22]), [475.862361, 106.646008]); |
| 2332 | + assert.allCloseTo(projection.invert([475.862361, 106.646008]), [-1.55, 47.22]); |
| 2333 | + }); |
| 2334 | + |
| 2335 | + it("is the same for 'mercator' and {type: 'mercator'}", () => { |
| 2336 | + const projection1 = Plot.plot({projection: "mercator", marks: [Plot.graticule()]}).scale("projection"); |
| 2337 | + const projection2 = Plot.plot({projection: {type: "mercator"}, marks: [Plot.graticule()]}).scale("projection"); |
| 2338 | + assert.allCloseTo(projection1.apply([-1.55, 47.22]), projection2.apply([-1.55, 47.22])); |
| 2339 | + assert.allCloseTo(projection1.invert([316.74875, 224.179291]), projection2.invert([316.74875, 224.179291])); |
| 2340 | + }); |
| 2341 | + |
| 2342 | + it("exposes apply and invert for identity", () => { |
| 2343 | + const domain = { |
| 2344 | + type: "Polygon", |
| 2345 | + coordinates: [ |
| 2346 | + [ |
| 2347 | + [0, 0], |
| 2348 | + [200, 0], |
| 2349 | + [200, 100], |
| 2350 | + [0, 100], |
| 2351 | + [0, 0] |
| 2352 | + ] |
| 2353 | + ] |
| 2354 | + }; |
| 2355 | + const plot = Plot.plot({ |
| 2356 | + width: 400, |
| 2357 | + height: 200, |
| 2358 | + margin: 0, |
| 2359 | + projection: {type: "identity", domain}, |
| 2360 | + marks: [Plot.frame()] |
| 2361 | + }); |
| 2362 | + const p = plot.scale("projection"); |
| 2363 | + assert.allCloseTo(p.apply([0, 0]), [0, 0]); |
| 2364 | + assert.allCloseTo(p.apply([200, 100]), [400, 200]); |
| 2365 | + assert.allCloseTo(p.apply([100, 50]), [200, 100]); |
| 2366 | + assert.allCloseTo(p.invert([0, 0]), [0, 0]); |
| 2367 | + assert.allCloseTo(p.invert([400, 200]), [200, 100]); |
| 2368 | + assert.allCloseTo(p.invert([200, 100]), [100, 50]); |
| 2369 | + }); |
| 2370 | + |
| 2371 | + it("exposes apply and invert for reflect-y", () => { |
| 2372 | + const domain = { |
| 2373 | + type: "Polygon", |
| 2374 | + coordinates: [ |
| 2375 | + [ |
| 2376 | + [0, 0], |
| 2377 | + [200, 0], |
| 2378 | + [200, 100], |
| 2379 | + [0, 100], |
| 2380 | + [0, 0] |
| 2381 | + ] |
| 2382 | + ] |
| 2383 | + }; |
| 2384 | + const plot = Plot.plot({ |
| 2385 | + width: 400, |
| 2386 | + height: 200, |
| 2387 | + margin: 0, |
| 2388 | + projection: {type: "reflect-y", domain}, |
| 2389 | + marks: [Plot.frame()] |
| 2390 | + }); |
| 2391 | + const p = plot.scale("projection"); |
| 2392 | + assert.allCloseTo(p.apply([0, 0]), [0, 200]); |
| 2393 | + assert.allCloseTo(p.apply([200, 100]), [400, 0]); |
| 2394 | + assert.allCloseTo(p.apply([100, 50]), [200, 100]); |
| 2395 | + assert.allCloseTo(p.invert([0, 200]), [0, 0]); |
| 2396 | + assert.allCloseTo(p.invert([400, 0]), [200, 100]); |
| 2397 | + assert.allCloseTo(p.invert([200, 100]), [100, 50]); |
| 2398 | + }); |
| 2399 | + |
| 2400 | + it("round-trips to a second plot", () => { |
| 2401 | + const plot1 = Plot.plot({projection: "mercator", marks: [Plot.graticule()]}); |
| 2402 | + const p1 = plot1.scale("projection"); |
| 2403 | + const plot2 = Plot.plot({projection: p1, marks: [Plot.graticule()]}); |
| 2404 | + const p2 = plot2.scale("projection"); |
| 2405 | + // Same dimensions, so pixel coordinates match |
| 2406 | + const point = [-1.55, 47.22]; |
| 2407 | + assert.allCloseTo(p1.apply(point), p2.apply(point)); |
| 2408 | + }); |
| 2409 | + |
| 2410 | + it("round-trips with different dimensions", () => { |
| 2411 | + const plot1 = Plot.plot({width: 640, projection: "mercator", marks: [Plot.graticule()]}); |
| 2412 | + const projection1 = plot1.scale("projection"); |
| 2413 | + const plot2 = Plot.plot({width: 300, projection: projection1, marks: [Plot.graticule()]}); |
| 2414 | + const projection2 = plot2.scale("projection"); |
| 2415 | + // Different dimensions, but pixel coordinates still match |
| 2416 | + assert.allCloseTo(projection1.apply([-1.55, 47.22]), [316.74875, 224.179291]); |
| 2417 | + assert.allCloseTo(projection2.apply([-1.55, 47.22]), [316.74875, 224.179291]); |
| 2418 | + // But invert still round-trips |
| 2419 | + assert.allCloseTo(projection2.invert(projection2.apply([-1.55, 47.22])), [-1.55, 47.22]); |
| 2420 | + }); |
| 2421 | +}); |
0 commit comments