|
| 1 | +<!doctype html> |
| 2 | +<notebook theme="midnight"> |
| 3 | + <title>Observable Notebooks Data loader test</title> |
| 4 | + <script id="1" type="text/markdown"> |
| 5 | + # Observable Notebooks<br> <span style="color: var(--theme-foreground-faint);">Data loader test</span> |
| 6 | + |
| 7 | + <link rel="stylesheet" href="./style.css"> |
| 8 | + </script> |
| 9 | + <script type="module" pinned=""> |
| 10 | + Plot.plot({ |
| 11 | + width: 960, |
| 12 | + projection: { |
| 13 | + type: "transverse-mercator", |
| 14 | + domain: land, |
| 15 | + rotate: [4, 0] |
| 16 | + }, |
| 17 | + color: { |
| 18 | + domain: d3.range(n * n), |
| 19 | + range: d3.cross(d3.schemeBlues[n], d3.schemeOranges[n]).map(mixblend) |
| 20 | + }, |
| 21 | + marks: [ |
| 22 | + Plot.raster({length: ppt.length}, { |
| 23 | + x: lon, |
| 24 | + y: lat, |
| 25 | + fill: 0, |
| 26 | + interpolate: interpolateBivariate(n, ppt, temp), |
| 27 | + clip: land |
| 28 | + }), |
| 29 | + Plot.geo(land, {strokeWidth: 0.5}), |
| 30 | + (index, {scales}) => |
| 31 | + Plot.plot({ |
| 32 | + color: scales.color, |
| 33 | + axis: null, |
| 34 | + inset: 18, |
| 35 | + width: 136, |
| 36 | + height: 136, |
| 37 | + padding: 0, |
| 38 | + y: {reverse: true}, |
| 39 | + style: "transform-origin: 68px 68px; transform: rotate(-45deg) translate(0px, 20px)", |
| 40 | + marks: [ |
| 41 | + Plot.cell(d3.cross(d3.range(n), d3.range(n)), { |
| 42 | + fill: ([a, b]) => n * a + b, |
| 43 | + inset: -0.5 |
| 44 | + }), |
| 45 | + Plot.text(["Precipitation →"], { |
| 46 | + frameAnchor: "bottom-right", |
| 47 | + fontWeight: 600, |
| 48 | + dx: -18, |
| 49 | + dy: -6 |
| 50 | + }), |
| 51 | + Plot.text(["← Temperature"], { |
| 52 | + frameAnchor: "top-left", |
| 53 | + fontWeight: 600, |
| 54 | + rotate: 90, |
| 55 | + dx: 12, |
| 56 | + dy: 18 |
| 57 | + }) |
| 58 | + ] |
| 59 | + }) |
| 60 | + ] |
| 61 | + }) |
| 62 | + </script> |
| 63 | + <script type="module" pinned=""> |
| 64 | + function interpolateBivariate(n, v1, v2) { |
| 65 | + const r = d3.range(n); |
| 66 | + const s1 = d3.scaleQuantile(v1, r); |
| 67 | + const s2 = d3.scaleQuantile(v2, r); |
| 68 | + const interpolate = Plot.interpolatorBarycentric(); |
| 69 | + return (I, w, h, X, Y) => { |
| 70 | + I = I.filter((i) => !isNaN(v1[i]) && !isNaN(v2[i])); |
| 71 | + const V1 = interpolate(I, w, h, X, Y, v1); |
| 72 | + const V2 = interpolate(I, w, h, X, Y, v2); |
| 73 | + return Uint8Array.from(V1, (_, i) => n * s1(V1[i]) + s2(V2[i])); |
| 74 | + }; |
| 75 | + } |
| 76 | + </script> |
| 77 | + <script type="module" pinned=""> |
| 78 | + const n = 7; // number of color classes |
| 79 | + </script> |
| 80 | + <script type="module" pinned=""> |
| 81 | + function mixblend([a, b]) { |
| 82 | + a = d3.rgb(a); |
| 83 | + b = d3.rgb(b); |
| 84 | + const l = Math.min(255, b.r + b.g + b.b); |
| 85 | + a.r *= b.r / l; |
| 86 | + a.g *= b.g / l; |
| 87 | + a.b *= b.b / l; |
| 88 | + return a; |
| 89 | + } |
| 90 | + </script> |
| 91 | + <script type="module" pinned=""> |
| 92 | + import {NetCDFReader} from "npm:netcdfjs"; |
| 93 | + |
| 94 | + const tmaxReader = new NetCDFReader(tmaxBuffer); |
| 95 | + const tminReader = new NetCDFReader(tminBuffer); |
| 96 | + const pptReader = new NetCDFReader(pptBuffer); |
| 97 | + const tmax = Float32Array.from(tmaxReader.getDataVariable("tmax"), d => d !== -32768 ? d * 0.01 - 99 : NaN); |
| 98 | + const tmin = Float32Array.from(tminReader.getDataVariable("tmin"), d => d !== -32768 ? d * 0.01 - 99 : NaN); |
| 99 | + const ppt = Float32Array.from(pptReader.getDataVariable("ppt"), d => d !== -2147483648 ? d * 0.1 : NaN).map(d => d < 10 ? NaN : d); |
| 100 | + const lx = tmaxReader.getDataVariable("lon"); |
| 101 | + const ly = tmaxReader.getDataVariable("lat"); |
| 102 | + const l = lx.length; |
| 103 | + const temp = tmax.map((max, i) => (max + tmin[i]) / 2); |
| 104 | + const lon = (d, i) => lx[i % l]; |
| 105 | + const lat = (d, i) => ly[i/ l | 0]; |
| 106 | + </script> |
| 107 | + <script type="application/vnd.node.javascript" pinned="" hidden="" output="tmaxBuffer"> |
| 108 | + import {Readable} from "node:stream"; |
| 109 | + |
| 110 | + fetch("http://thredds.northwestknowledge.net:8080/thredds/ncss/agg_terraclimate_tmax_1958_CurrentYear_GLOBE.nc?var=tmax&south=49.674&north=61.061&west=-14.015517&east=2.0919117&disableProjSubset=on&addLatLon=true&horizStride=1&accept=netcdf") |
| 111 | + .then((response) => response.ok ? response.body : Promise.reject(response.status)) |
| 112 | + .then((body) => Readable.from(body).pipe(process.stdout)); |
| 113 | + </script> |
| 114 | + <script type="application/vnd.node.javascript" pinned="" hidden="" output="tminBuffer"> |
| 115 | + import {Readable} from "node:stream"; |
| 116 | + |
| 117 | + fetch("http://thredds.northwestknowledge.net:8080/thredds/ncss/agg_terraclimate_tmin_1958_CurrentYear_GLOBE.nc?var=tmin&south=49.674&north=61.061&west=-14.015517&east=2.0919117&disableProjSubset=on&addLatLon=true&horizStride=1&accept=netcdf") |
| 118 | + .then((response) => response.ok ? response.body : Promise.reject(response.status)) |
| 119 | + .then((body) => Readable.from(body).pipe(process.stdout)); |
| 120 | + </script> |
| 121 | + <script type="application/vnd.node.javascript" pinned="" hidden="" output="pptBuffer"> |
| 122 | + import {Readable} from "node:stream"; |
| 123 | + |
| 124 | + fetch("http://thredds.northwestknowledge.net:8080/thredds/ncss/agg_terraclimate_ppt_1958_CurrentYear_GLOBE.nc?var=ppt&south=49.674&north=61.061&west=-14.015517&east=2.0919117&disableProjSubset=on&addLatLon=true&horizStride=1&accept=netcdf") |
| 125 | + .then((response) => response.ok ? response.body : Promise.reject(response.status)) |
| 126 | + .then((body) => Readable.from(body).pipe(process.stdout)); |
| 127 | + </script> |
| 128 | + <script type="module" pinned=""> |
| 129 | + const land = fetch("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-10m.json") |
| 130 | + .then((response) => response.ok ? response.json() : Promise.reject(response.status)) |
| 131 | + .then((world) => topojson.feature(world, {type: "GeometryCollection", geometries: world.objects.countries.geometries.filter(({id}) => id === "826" || id === "372")})); |
| 132 | + </script> |
| 133 | +</notebook> |
0 commit comments