[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"extends\": \"eslint:recommended\",\n  \"parserOptions\": {\n    \"sourceType\": \"module\",\n    \"ecmaVersion\": 2020\n  },\n  \"env\": {\n    \"es6\": true,\n    \"node\": true,\n    \"browser\": true\n  },\n  \"rules\": {\n    \"no-cond-assign\": 0,\n    \"no-constant-condition\": 0,\n    \"no-sparse-arrays\": 0,\n    \"no-unexpected-multiline\": 0,\n    \"comma-dangle\": [\"error\", \"never\"],\n    \"semi\": [2, \"always\"]\n  }\n}\n"
  },
  {
    "path": ".github/eslint.json",
    "content": "{\n  \"problemMatcher\": [\n    {\n      \"owner\": \"eslint-compact\",\n      \"pattern\": [\n        {\n          \"regexp\": \"^(.+):\\\\sline\\\\s(\\\\d+),\\\\scol\\\\s(\\\\d+),\\\\s(Error|Warning|Info)\\\\s-\\\\s(.+)\\\\s\\\\((.+)\\\\)$\",\n          \"file\": 1,\n          \"line\": 2,\n          \"column\": 3,\n          \"severity\": 4,\n          \"message\": 5,\n          \"code\": 6\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "# https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions\n\nname: Node.js CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [14.x]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v1\n      with:\n        node-version: ${{ matrix.node-version }}\n    - run: yarn --frozen-lockfile\n    - run: |\n        echo ::add-matcher::.github/eslint.json\n        yarn run eslint src test --format=compact\n    - run: yarn test\n"
  },
  {
    "path": ".gitignore",
    "content": "*.sublime-workspace\n.DS_Store\ndist/\nnode_modules\nnpm-debug.log\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2012-2023 Mike Bostock\n\nPermission to use, copy, modify, and/or distribute this software for any purpose\nwith or without fee is hereby granted, provided that the above copyright notice\nand this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\nTHIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# d3-contour\n\n<a href=\"https://d3js.org\"><img src=\"https://github.com/d3/d3/raw/main/docs/public/logo.svg\" width=\"256\" height=\"256\"></a>\n\nThis module computes contour polygons by applying [marching squares](https://en.wikipedia.org/wiki/Marching_squares) to a rectangular array of numeric values.\n\n## Resources\n\n- [Documentation](https://d3js.org/d3-contour)\n- [Examples](https://observablehq.com/collection/@d3/d3-contour)\n- [Releases](https://github.com/d3/d3-contour/releases)\n- [Getting help](https://d3js.org/community)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"d3-contour\",\n  \"version\": \"4.0.2\",\n  \"description\": \"Compute contour polygons using marching squares.\",\n  \"homepage\": \"https://d3js.org/d3-contour/\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/d3/d3-contour.git\"\n  },\n  \"keywords\": [\n    \"d3\",\n    \"d3-module\",\n    \"contour\",\n    \"isoline\"\n  ],\n  \"license\": \"ISC\",\n  \"author\": {\n    \"name\": \"Mike Bostock\",\n    \"url\": \"http://bost.ocks.org/mike\"\n  },\n  \"type\": \"module\",\n  \"files\": [\n    \"dist/**/*.js\",\n    \"src/**/*.js\"\n  ],\n  \"module\": \"src/index.js\",\n  \"main\": \"src/index.js\",\n  \"jsdelivr\": \"dist/d3-contour.min.js\",\n  \"unpkg\": \"dist/d3-contour.min.js\",\n  \"exports\": {\n    \"umd\": \"./dist/d3-contour.min.js\",\n    \"default\": \"./src/index.js\"\n  },\n  \"_moduleAliases\": {\n    \"d3-contour\": \"./src/index.js\"\n  },\n  \"sideEffects\": false,\n  \"dependencies\": {\n    \"d3-array\": \"^3.2.0\"\n  },\n  \"devDependencies\": {\n    \"d3-axis\": \"3\",\n    \"d3-dsv\": \"3\",\n    \"d3-fetch\": \"3\",\n    \"d3-geo\": \"3\",\n    \"d3-polygon\": \"3\",\n    \"d3-scale\": \"4\",\n    \"d3-selection\": \"3\",\n    \"eslint\": \"8\",\n    \"htl\": \"^0.3.1\",\n    \"js-beautify\": \"1\",\n    \"jsdom\": \"20\",\n    \"mocha\": \"10\",\n    \"module-alias\": \"2\",\n    \"rollup\": \"3\",\n    \"rollup-plugin-terser\": \"7\"\n  },\n  \"scripts\": {\n    \"test\": \"mkdir -p test/output && mocha -r module-alias/register 'test/**/*-test.js' test/snapshot.js && eslint src test\",\n    \"prepublishOnly\": \"rm -rf dist && rollup -c\",\n    \"postpublish\": \"git push && git push --tags && cd ../d3.github.com && git pull && cp ../${npm_package_name}/dist/${npm_package_name}.js ${npm_package_name}.v${npm_package_version%%.*}.js && cp ../${npm_package_name}/dist/${npm_package_name}.min.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git add ${npm_package_name}.v${npm_package_version%%.*}.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git commit -m \\\"${npm_package_name} ${npm_package_version}\\\" && git push && cd -\"\n  },\n  \"engines\": {\n    \"node\": \">=12\"\n  }\n}\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import {readFileSync} from \"fs\";\nimport {terser} from \"rollup-plugin-terser\";\nimport meta from \"./package.json\" assert {type: \"json\"};\n\n// Extract copyrights from the LICENSE.\nconst copyright = readFileSync(\"./LICENSE\", \"utf-8\")\n  .split(/\\n/g)\n  .filter(line => /^Copyright\\s+/.test(line))\n  .map(line => line.replace(/^Copyright\\s+/, \"\"))\n  .join(\", \");\n\nconst config = {\n  input: \"src/index.js\",\n  external: Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)),\n  output: {\n    file: `dist/${meta.name}.js`,\n    name: \"d3\",\n    format: \"umd\",\n    indent: false,\n    extend: true,\n    banner: `// ${meta.homepage} v${meta.version} Copyright ${copyright}`,\n    globals: Object.assign({}, ...Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)).map(key => ({[key]: \"d3\"})))\n  },\n  plugins: []\n};\n\nexport default [\n  config,\n  {\n    ...config,\n    output: {\n      ...config.output,\n      file: `dist/${meta.name}.min.js`\n    },\n    plugins: [\n      ...config.plugins,\n      terser({\n        output: {\n          preamble: config.output.banner\n        }\n      })\n    ]\n  }\n];\n"
  },
  {
    "path": "src/area.js",
    "content": "export default function(ring) {\n  var i = 0, n = ring.length, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];\n  while (++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];\n  return area;\n}\n"
  },
  {
    "path": "src/array.js",
    "content": "var array = Array.prototype;\n\nexport var slice = array.slice;\n"
  },
  {
    "path": "src/ascending.js",
    "content": "export default function(a, b) {\n  return a - b;\n}\n"
  },
  {
    "path": "src/constant.js",
    "content": "export default x => () => x;\n"
  },
  {
    "path": "src/contains.js",
    "content": "export default function(ring, hole) {\n  var i = -1, n = hole.length, c;\n  while (++i < n) if (c = ringContains(ring, hole[i])) return c;\n  return 0;\n}\n\nfunction ringContains(ring, point) {\n  var x = point[0], y = point[1], contains = -1;\n  for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {\n    var pi = ring[i], xi = pi[0], yi = pi[1], pj = ring[j], xj = pj[0], yj = pj[1];\n    if (segmentContains(pi, pj, point)) return 0;\n    if (((yi > y) !== (yj > y)) && ((x < (xj - xi) * (y - yi) / (yj - yi) + xi))) contains = -contains;\n  }\n  return contains;\n}\n\nfunction segmentContains(a, b, c) {\n  var i; return collinear(a, b, c) && within(a[i = +(a[0] === b[0])], c[i], b[i]);\n}\n\nfunction collinear(a, b, c) {\n  return (b[0] - a[0]) * (c[1] - a[1]) === (c[0] - a[0]) * (b[1] - a[1]);\n}\n\nfunction within(p, q, r) {\n  return p <= q && q <= r || r <= q && q <= p;\n}\n"
  },
  {
    "path": "src/contours.js",
    "content": "import {extent, nice, thresholdSturges, ticks} from \"d3-array\";\nimport {slice} from \"./array.js\";\nimport ascending from \"./ascending.js\";\nimport area from \"./area.js\";\nimport constant from \"./constant.js\";\nimport contains from \"./contains.js\";\nimport noop from \"./noop.js\";\n\nvar cases = [\n  [],\n  [[[1.0, 1.5], [0.5, 1.0]]],\n  [[[1.5, 1.0], [1.0, 1.5]]],\n  [[[1.5, 1.0], [0.5, 1.0]]],\n  [[[1.0, 0.5], [1.5, 1.0]]],\n  [[[1.0, 1.5], [0.5, 1.0]], [[1.0, 0.5], [1.5, 1.0]]],\n  [[[1.0, 0.5], [1.0, 1.5]]],\n  [[[1.0, 0.5], [0.5, 1.0]]],\n  [[[0.5, 1.0], [1.0, 0.5]]],\n  [[[1.0, 1.5], [1.0, 0.5]]],\n  [[[0.5, 1.0], [1.0, 0.5]], [[1.5, 1.0], [1.0, 1.5]]],\n  [[[1.5, 1.0], [1.0, 0.5]]],\n  [[[0.5, 1.0], [1.5, 1.0]]],\n  [[[1.0, 1.5], [1.5, 1.0]]],\n  [[[0.5, 1.0], [1.0, 1.5]]],\n  []\n];\n\nexport default function() {\n  var dx = 1,\n      dy = 1,\n      threshold = thresholdSturges,\n      smooth = smoothLinear;\n\n  function contours(values) {\n    var tz = threshold(values);\n\n    // Convert number of thresholds into uniform thresholds.\n    if (!Array.isArray(tz)) {\n      const e = extent(values, finite);\n      tz = ticks(...nice(e[0], e[1], tz), tz);\n      while (tz[tz.length - 1] >= e[1]) tz.pop();\n      while (tz[1] < e[0]) tz.shift();\n    } else {\n      tz = tz.slice().sort(ascending);\n    }\n\n    return tz.map(value => contour(values, value));\n  }\n\n  // Accumulate, smooth contour rings, assign holes to exterior rings.\n  // Based on https://github.com/mbostock/shapefile/blob/v0.6.2/shp/polygon.js\n  function contour(values, value) {\n    const v = value == null ? NaN : +value;\n    if (isNaN(v)) throw new Error(`invalid value: ${value}`);\n\n    var polygons = [],\n        holes = [];\n\n    isorings(values, v, function(ring) {\n      smooth(ring, values, v);\n      if (area(ring) > 0) polygons.push([ring]);\n      else holes.push(ring);\n    });\n\n    holes.forEach(function(hole) {\n      for (var i = 0, n = polygons.length, polygon; i < n; ++i) {\n        if (contains((polygon = polygons[i])[0], hole) !== -1) {\n          polygon.push(hole);\n          return;\n        }\n      }\n    });\n\n    return {\n      type: \"MultiPolygon\",\n      value: value,\n      coordinates: polygons\n    };\n  }\n\n  // Marching squares with isolines stitched into rings.\n  // Based on https://github.com/topojson/topojson-client/blob/v3.0.0/src/stitch.js\n  function isorings(values, value, callback) {\n    var fragmentByStart = new Array,\n        fragmentByEnd = new Array,\n        x, y, t0, t1, t2, t3;\n\n    // Special case for the first row (y = -1, t2 = t3 = 0).\n    x = y = -1;\n    t1 = above(values[0], value);\n    cases[t1 << 1].forEach(stitch);\n    while (++x < dx - 1) {\n      t0 = t1, t1 = above(values[x + 1], value);\n      cases[t0 | t1 << 1].forEach(stitch);\n    }\n    cases[t1 << 0].forEach(stitch);\n\n    // General case for the intermediate rows.\n    while (++y < dy - 1) {\n      x = -1;\n      t1 = above(values[y * dx + dx], value);\n      t2 = above(values[y * dx], value);\n      cases[t1 << 1 | t2 << 2].forEach(stitch);\n      while (++x < dx - 1) {\n        t0 = t1, t1 = above(values[y * dx + dx + x + 1], value);\n        t3 = t2, t2 = above(values[y * dx + x + 1], value);\n        cases[t0 | t1 << 1 | t2 << 2 | t3 << 3].forEach(stitch);\n      }\n      cases[t1 | t2 << 3].forEach(stitch);\n    }\n\n    // Special case for the last row (y = dy - 1, t0 = t1 = 0).\n    x = -1;\n    t2 = values[y * dx] >= value;\n    cases[t2 << 2].forEach(stitch);\n    while (++x < dx - 1) {\n      t3 = t2, t2 = above(values[y * dx + x + 1], value);\n      cases[t2 << 2 | t3 << 3].forEach(stitch);\n    }\n    cases[t2 << 3].forEach(stitch);\n\n    function stitch(line) {\n      var start = [line[0][0] + x, line[0][1] + y],\n          end = [line[1][0] + x, line[1][1] + y],\n          startIndex = index(start),\n          endIndex = index(end),\n          f, g;\n      if (f = fragmentByEnd[startIndex]) {\n        if (g = fragmentByStart[endIndex]) {\n          delete fragmentByEnd[f.end];\n          delete fragmentByStart[g.start];\n          if (f === g) {\n            f.ring.push(end);\n            callback(f.ring);\n          } else {\n            fragmentByStart[f.start] = fragmentByEnd[g.end] = {start: f.start, end: g.end, ring: f.ring.concat(g.ring)};\n          }\n        } else {\n          delete fragmentByEnd[f.end];\n          f.ring.push(end);\n          fragmentByEnd[f.end = endIndex] = f;\n        }\n      } else if (f = fragmentByStart[endIndex]) {\n        if (g = fragmentByEnd[startIndex]) {\n          delete fragmentByStart[f.start];\n          delete fragmentByEnd[g.end];\n          if (f === g) {\n            f.ring.push(end);\n            callback(f.ring);\n          } else {\n            fragmentByStart[g.start] = fragmentByEnd[f.end] = {start: g.start, end: f.end, ring: g.ring.concat(f.ring)};\n          }\n        } else {\n          delete fragmentByStart[f.start];\n          f.ring.unshift(start);\n          fragmentByStart[f.start = startIndex] = f;\n        }\n      } else {\n        fragmentByStart[startIndex] = fragmentByEnd[endIndex] = {start: startIndex, end: endIndex, ring: [start, end]};\n      }\n    }\n  }\n\n  function index(point) {\n    return point[0] * 2 + point[1] * (dx + 1) * 4;\n  }\n\n  function smoothLinear(ring, values, value) {\n    ring.forEach(function(point) {\n      var x = point[0],\n          y = point[1],\n          xt = x | 0,\n          yt = y | 0,\n          v1 = valid(values[yt * dx + xt]);\n      if (x > 0 && x < dx && xt === x) {\n        point[0] = smooth1(x, valid(values[yt * dx + xt - 1]), v1, value);\n      }\n      if (y > 0 && y < dy && yt === y) {\n        point[1] = smooth1(y, valid(values[(yt - 1) * dx + xt]), v1, value);\n      }\n    });\n  }\n\n  contours.contour = contour;\n\n  contours.size = function(_) {\n    if (!arguments.length) return [dx, dy];\n    var _0 = Math.floor(_[0]), _1 = Math.floor(_[1]);\n    if (!(_0 >= 0 && _1 >= 0)) throw new Error(\"invalid size\");\n    return dx = _0, dy = _1, contours;\n  };\n\n  contours.thresholds = function(_) {\n    return arguments.length ? (threshold = typeof _ === \"function\" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), contours) : threshold;\n  };\n\n  contours.smooth = function(_) {\n    return arguments.length ? (smooth = _ ? smoothLinear : noop, contours) : smooth === smoothLinear;\n  };\n\n  return contours;\n}\n\n// When computing the extent, ignore infinite values (as well as invalid ones).\nfunction finite(x) {\n  return isFinite(x) ? x : NaN;\n}\n\n// Is the (possibly invalid) x greater than or equal to the (known valid) value?\n// Treat any invalid value as below negative infinity.\nfunction above(x, value) {\n  return x == null ? false : +x >= value;\n}\n\n// During smoothing, treat any invalid value as negative infinity.\nfunction valid(v) {\n  return v == null || isNaN(v = +v) ? -Infinity : v;\n}\n\nfunction smooth1(x, v0, v1, value) {\n  const a = value - v0;\n  const b = v1 - v0;\n  const d = isFinite(a) || isFinite(b) ? a / b : Math.sign(a) / Math.sign(b);\n  return isNaN(d) ? x : x + d - 0.5;\n}\n"
  },
  {
    "path": "src/density.js",
    "content": "import {blur2, max, ticks} from \"d3-array\";\nimport {slice} from \"./array.js\";\nimport constant from \"./constant.js\";\nimport Contours from \"./contours.js\";\n\nfunction defaultX(d) {\n  return d[0];\n}\n\nfunction defaultY(d) {\n  return d[1];\n}\n\nfunction defaultWeight() {\n  return 1;\n}\n\nexport default function() {\n  var x = defaultX,\n      y = defaultY,\n      weight = defaultWeight,\n      dx = 960,\n      dy = 500,\n      r = 20, // blur radius\n      k = 2, // log2(grid cell size)\n      o = r * 3, // grid offset, to pad for blur\n      n = (dx + o * 2) >> k, // grid width\n      m = (dy + o * 2) >> k, // grid height\n      threshold = constant(20);\n\n  function grid(data) {\n    var values = new Float32Array(n * m),\n        pow2k = Math.pow(2, -k),\n        i = -1;\n\n    for (const d of data) {\n      var xi = (x(d, ++i, data) + o) * pow2k,\n          yi = (y(d, i, data) + o) * pow2k,\n          wi = +weight(d, i, data);\n      if (wi && xi >= 0 && xi < n && yi >= 0 && yi < m) {\n        var x0 = Math.floor(xi),\n            y0 = Math.floor(yi),\n            xt = xi - x0 - 0.5,\n            yt = yi - y0 - 0.5;\n        values[x0 + y0 * n] += (1 - xt) * (1 - yt) * wi;\n        values[x0 + 1 + y0 * n] += xt * (1 - yt) * wi;\n        values[x0 + 1 + (y0 + 1) * n] += xt * yt * wi;\n        values[x0 + (y0 + 1) * n] += (1 - xt) * yt * wi;\n      }\n    }\n\n    blur2({data: values, width: n, height: m}, r * pow2k);\n    return values;\n  }\n\n  function density(data) {\n    var values = grid(data),\n        tz = threshold(values),\n        pow4k = Math.pow(2, 2 * k);\n\n    // Convert number of thresholds into uniform thresholds.\n    if (!Array.isArray(tz)) {\n      tz = ticks(Number.MIN_VALUE, max(values) / pow4k, tz);\n    }\n\n    return Contours()\n        .size([n, m])\n        .thresholds(tz.map(d => d * pow4k))\n      (values)\n        .map((c, i) => (c.value = +tz[i], transform(c)));\n  }\n\n  density.contours = function(data) {\n    var values = grid(data),\n        contours = Contours().size([n, m]),\n        pow4k = Math.pow(2, 2 * k),\n        contour = value => {\n          value = +value;\n          var c = transform(contours.contour(values, value * pow4k));\n          c.value = value; // preserve exact threshold value\n          return c;\n        };\n    Object.defineProperty(contour, \"max\", {get: () => max(values) / pow4k});\n    return contour;\n  };\n\n  function transform(geometry) {\n    geometry.coordinates.forEach(transformPolygon);\n    return geometry;\n  }\n\n  function transformPolygon(coordinates) {\n    coordinates.forEach(transformRing);\n  }\n\n  function transformRing(coordinates) {\n    coordinates.forEach(transformPoint);\n  }\n\n  // TODO Optimize.\n  function transformPoint(coordinates) {\n    coordinates[0] = coordinates[0] * Math.pow(2, k) - o;\n    coordinates[1] = coordinates[1] * Math.pow(2, k) - o;\n  }\n\n  function resize() {\n    o = r * 3;\n    n = (dx + o * 2) >> k;\n    m = (dy + o * 2) >> k;\n    return density;\n  }\n\n  density.x = function(_) {\n    return arguments.length ? (x = typeof _ === \"function\" ? _ : constant(+_), density) : x;\n  };\n\n  density.y = function(_) {\n    return arguments.length ? (y = typeof _ === \"function\" ? _ : constant(+_), density) : y;\n  };\n\n  density.weight = function(_) {\n    return arguments.length ? (weight = typeof _ === \"function\" ? _ : constant(+_), density) : weight;\n  };\n\n  density.size = function(_) {\n    if (!arguments.length) return [dx, dy];\n    var _0 = +_[0], _1 = +_[1];\n    if (!(_0 >= 0 && _1 >= 0)) throw new Error(\"invalid size\");\n    return dx = _0, dy = _1, resize();\n  };\n\n  density.cellSize = function(_) {\n    if (!arguments.length) return 1 << k;\n    if (!((_ = +_) >= 1)) throw new Error(\"invalid cell size\");\n    return k = Math.floor(Math.log(_) / Math.LN2), resize();\n  };\n\n  density.thresholds = function(_) {\n    return arguments.length ? (threshold = typeof _ === \"function\" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), density) : threshold;\n  };\n\n  density.bandwidth = function(_) {\n    if (!arguments.length) return Math.sqrt(r * (r + 1));\n    if (!((_ = +_) >= 0)) throw new Error(\"invalid bandwidth\");\n    return r = (Math.sqrt(4 * _ * _ + 1) - 1) / 2, resize();\n  };\n\n  return density;\n}\n"
  },
  {
    "path": "src/index.js",
    "content": "export {default as contours} from \"./contours.js\";\nexport {default as contourDensity} from \"./density.js\";\n"
  },
  {
    "path": "src/noop.js",
    "content": "export default function() {}\n"
  },
  {
    "path": "test/.eslintrc.json",
    "content": "{\n  \"env\": {\n    \"mocha\": true\n  }\n}\n"
  },
  {
    "path": "test/asserts.js",
    "content": "import assert from \"assert\";\n\nexport function assertInDelta(actual, expected, delta = 1e-6) {\n  assert(inDelta(actual, expected, delta));\n}\n\nfunction inDelta(actual, expected, delta) {\n  return (Array.isArray(expected) ? inDeltaArray : inDeltaNumber)(actual, expected, delta);\n}\n\nfunction inDeltaArray(actual, expected, delta) {\n  const n = expected.length;\n  if (actual.length !== n) return false;\n  for (let i = 0; i < n; ++i) if (!inDelta(actual[i], expected[i], delta)) return false;\n  return true;\n}\n\nfunction inDeltaNumber(actual, expected, delta) {\n  return actual >= expected - delta && actual <= expected + delta;\n}\n"
  },
  {
    "path": "test/contours-test.js",
    "content": "import assert from \"assert\";\nimport {contours} from \"../src/index.js\";\n\nit(\"contours(values) returns the expected result for an empty polygon\", () => {\n  const c = contours().size([10, 10]).thresholds([0.5]);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]), [\n    {\n      \"type\": \"MultiPolygon\",\n      \"value\": 0.5,\n      \"coordinates\": []\n    }\n  ]);\n});\n\nit(\"contours(values) returns the expected result for a simple polygon\", () => {\n  const c = contours().size([10, 10]).thresholds([0.5]);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]), [\n    {\n      \"type\": \"MultiPolygon\",\n      \"value\": 0.5,\n      \"coordinates\": [\n        [\n          [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],\n           [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],\n           [4.5, 8], [5.5, 8], [6, 7.5]]\n        ]\n      ]\n    }\n  ]);\n});\n\nit(\"contours(values).contour(value) returns the expected result for a simple polygon\", () => {\n  const c = contours().size([10, 10]);\n  assert.deepStrictEqual(c.contour([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ], 0.5), {\n    \"type\": \"MultiPolygon\",\n    \"value\": 0.5,\n    \"coordinates\": [\n      [\n        [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],\n         [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],\n         [4.5, 8], [5.5, 8], [6, 7.5]]\n      ]\n    ]\n  });\n});\n\nit(\"contours.smooth(false)(values) returns the expected result for a simple polygon\", () => {\n  const c = contours().smooth(false).size([10, 10]).thresholds([0.5]);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 2, 1, 2, 0, 0, 0, 0,\n    0, 0, 0, 2, 2, 2, 0, 0, 0, 0,\n    0, 0, 0, 1, 2, 1, 0, 0, 0, 0,\n    0, 0, 0, 2, 2, 2, 0, 0, 0, 0,\n    0, 0, 0, 2, 1, 2, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]), [\n    {\n      \"type\": \"MultiPolygon\",\n      \"value\": 0.5,\n      \"coordinates\": [\n        [\n          [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],\n           [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],\n           [4.5, 8], [5.5, 8], [6, 7.5]]\n        ]\n      ]\n    }\n  ]);\n});\n\nit(\"contours(values) returns the expected result for a polygon with a hole\", () => {\n  const c = contours().size([10, 10]).thresholds([0.5]);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 0, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 0, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 0, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]), [\n    {\n      \"type\": \"MultiPolygon\",\n      \"value\": 0.5,\n      \"coordinates\": [\n        [\n          [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],\n           [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],\n           [4.5, 8], [5.5, 8], [6, 7.5]],\n          [[4.5, 7], [4, 6.5], [4, 5.5], [4, 4.5], [4.5, 4], [5, 4.5], [5, 5.5],\n           [5, 6.5], [4.5, 7]]\n        ]\n      ]\n    }\n  ]);\n});\n\nit(\"contours(values) returns the expected result for a multipolygon\", () => {\n  const c = contours().size([10, 10]).thresholds([0.5]);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,\n    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,\n    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,\n    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,\n    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]), [\n    {\n      \"type\": \"MultiPolygon\",\n      \"value\": 0.5,\n      \"coordinates\": [\n        [\n          [[5, 7.5], [5, 6.5], [5, 5.5], [5, 4.5], [5, 3.5], [4.5, 3], [3.5, 3],\n           [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8], [4.5, 8],\n           [5, 7.5]]\n        ],\n        [\n          [[7, 7.5], [7, 6.5], [7, 5.5], [7, 4.5], [7, 3.5], [6.5, 3], [6, 3.5],\n           [6, 4.5], [6, 5.5], [6, 6.5], [6, 7.5], [6.5, 8], [7, 7.5]]\n        ]\n      ]\n    }\n  ]);\n});\n\nit(\"contours(values) returns the expected result for a multipolygon with holes\", () => {\n  const c = contours().size([10, 10]).thresholds([0.5]);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,\n    0, 1, 0, 1, 0, 1, 0, 1, 0, 0,\n    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]), [\n    {\n      \"type\": \"MultiPolygon\",\n      \"value\": 0.5,\n      \"coordinates\": [\n        [\n          [[4, 5.5], [4, 4.5], [4, 3.5], [3.5, 3], [2.5, 3], [1.5, 3], [1, 3.5],\n           [1, 4.5], [1, 5.5], [1.5, 6], [2.5, 6], [3.5, 6], [4, 5.5]],\n          [[2.5, 5], [2, 4.5], [2.5, 4], [3, 4.5], [2.5, 5]]\n        ],\n        [\n          [[8, 5.5], [8, 4.5], [8, 3.5], [7.5, 3], [6.5, 3], [5.5, 3], [5, 3.5],\n           [5, 4.5], [5, 5.5], [5.5, 6], [6.5, 6], [7.5, 6], [8, 5.5]],\n          [[6.5, 5], [6, 4.5], [6.5, 4], [7, 4.5], [6.5, 5]]\n        ]\n      ]\n    }\n  ]);\n});\n\nit(\"contours.size(…) validates the specified size\", () => {\n  assert.deepStrictEqual(contours().size([1, 2]).size(), [1, 2]);\n  assert.deepStrictEqual(contours().size([0, 0]).size(), [0, 0]);\n  assert.deepStrictEqual(contours().size([1.5, 2.5]).size(), [1, 2]);\n  assert.throws(() => void contours().size([0, -1]), /invalid size/);\n});\n\nit(\"contours(values) returns the expected thresholds\", () => {\n  const c = contours().size([10, 10]).thresholds(20);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,\n    0, 1, 0, 1, 0, 1, 0, 1, 0, 0,\n    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]).map(d => d.value), [0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95]);\n});\n\nit(\"contours(values) ignores infinite values when computing the thresholds\", () => {\n  const c = contours().size([10, 10]).thresholds(20);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, -Infinity, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,\n    0, 1, 0, 1, 0, 1, 0, 1, 0, 0,\n    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, Infinity, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]).map(d => d.value), [0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95]);\n});\n\nit(\"contours(values) treats null, undefined, NaN and -Infinity as holes\", () => {\n  const c = contours().size([10, 10]);\n  assert.deepStrictEqual(c.contour([\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, -Infinity, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, null, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n    1, 1, 1, 1, 1, 1, 2, 2, 2, 1,\n    1, 1, NaN, 1, 1, 1, 2, -Infinity, 2, 1,\n    1, 1, 1, 1, 1, 1, 2, 2, 2, 1,\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1\n  ], 0), {\"type\":\"MultiPolygon\",\"value\":0,\"coordinates\":[[[[10,9.5],[10,8.5],[10,7.5],[10,6.5],[10,5.5],[10,4.5],[10,3.5],[10,2.5],[10,1.5],[10,0.5],[9.5,0],[8.5,0],[7.5,0],[6.5,0],[5.5,0],[4.5,0],[3.5,0],[2.5,0],[1.5,0],[0.5,0],[0,0.5],[0,1.5],[0,2.5],[0,3.5],[0,4.5],[0,5.5],[0,6.5],[0,7.5],[0,8.5],[0,9.5],[0.5,10],[1.5,10],[2.5,10],[3.5,10],[4.5,10],[5.5,10],[6.5,10],[7.5,10],[8.5,10],[9.5,10],[10,9.5]],[[1.5,2.5],[0.5,1.5],[1.5,0.5],[2.5,1.5],[1.5,2.5]],[[3.5,5.5],[2.5,4.5],[3.5,3.5],[4.5,4.5],[3.5,5.5]],[[2.5,8.5],[1.5,7.5],[2.5,6.5],[3.5,7.5],[2.5,8.5]],[[7.5,8.5],[6.5,7.5],[7.5,6.5],[8.5,7.5],[7.5,8.5]]]]});\n});\n\nit(\"contours(values) returns the expected result for a +Infinity value\", () => {\n  const c = contours().size([10, 10]).thresholds([0.5]);\n  assert.deepStrictEqual(c([\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, +Infinity, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, +Infinity, 1, 0, 0, 0, 0,\n    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n  ]), [\n    {\n      \"type\": \"MultiPolygon\",\n      \"value\": 0.5,\n      \"coordinates\": [\n        [\n          [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],\n           [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],\n           [4.5, 8], [5.5, 8], [6, 7.5]]\n        ]\n      ]\n    }\n  ]);\n});\n\nit(\"contour(values, invalid value) throws an error\", () => {\n  for (const value of [NaN, null, undefined, \"a string\"]) {\n    assert.throws(() => contours().size([3, 3]).contour([1, 2, 3, 4, 5, 6, 7, 8, 9], value),  /invalid value/);\n  }\n});\n\nit(\"contours(values) uses the expected nice thresholds\", () => {\n  assert.deepStrictEqual(contours().size([2, 1]).thresholds(14)([-149.76192742819748, 321.19300631539585]).map((c) => c.value), [-150, -100, -50, 0, 50, 100, 150, 200, 250, 300]);\n  assert.deepStrictEqual(contours().size([2, 1]).thresholds(5)([-149.76192742819748, 321.19300631539585]).map((c) => c.value), [-200, -100, 0, 100, 200, 300]);\n  assert.deepStrictEqual(contours().size([2, 1]).thresholds(14)([149.76192742819748, -321.19300631539585]).map((c) => c.value), [-350, -300, -250, -200, -150, -100, -50, 0, 50, 100]);\n  assert.deepStrictEqual(contours().size([2, 1]).thresholds(5)([149.76192742819748, -321.19300631539585]).map((c) => c.value), [-400, -300, -200, -100, 0, 100]);\n  assert.deepStrictEqual(contours().size([2, 1]).thresholds(12)([-29, 50]).map((c) => c.value), [-30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45]);\n  assert.deepStrictEqual(contours().size([2, 1]).thresholds(10)([-41, 245]).map((c) => c.value), [-50, 0, 50, 100, 150, 200]);\n  assert.deepStrictEqual(contours().size([2, 1]).thresholds(9)([-22, 242]).map((c) => c.value), [-50, 0, 50, 100, 150, 200]);\n});\n"
  },
  {
    "path": "test/data/faithful.tsv",
    "content": "eruptions\twaiting\n3.600\t79\n1.800\t54\n3.333\t74\n2.283\t62\n4.533\t85\n2.883\t55\n4.700\t88\n3.600\t85\n1.950\t51\n4.350\t85\n1.833\t54\n3.917\t84\n4.200\t78\n1.750\t47\n4.700\t83\n2.167\t52\n1.750\t62\n4.800\t84\n1.600\t52\n4.250\t79\n1.800\t51\n1.750\t47\n3.450\t78\n3.067\t69\n4.533\t74\n3.600\t83\n1.967\t55\n4.083\t76\n3.850\t78\n4.433\t79\n4.300\t73\n4.467\t77\n3.367\t66\n4.033\t80\n3.833\t74\n2.017\t52\n1.867\t48\n4.833\t80\n1.833\t59\n4.783\t90\n4.350\t80\n1.883\t58\n4.567\t84\n1.750\t58\n4.533\t73\n3.317\t83\n3.833\t64\n2.100\t53\n4.633\t82\n2.000\t59\n4.800\t75\n4.716\t90\n1.833\t54\n4.833\t80\n1.733\t54\n4.883\t83\n3.717\t71\n1.667\t64\n4.567\t77\n4.317\t81\n2.233\t59\n4.500\t84\n1.750\t48\n4.800\t82\n1.817\t60\n4.400\t92\n4.167\t78\n4.700\t78\n2.067\t65\n4.700\t73\n4.033\t82\n1.967\t56\n4.500\t79\n4.000\t71\n1.983\t62\n5.067\t76\n2.017\t60\n4.567\t78\n3.883\t76\n3.600\t83\n4.133\t75\n4.333\t82\n4.100\t70\n2.633\t65\n4.067\t73\n4.933\t88\n3.950\t76\n4.517\t80\n2.167\t48\n4.000\t86\n2.200\t60\n4.333\t90\n1.867\t50\n4.817\t78\n1.833\t63\n4.300\t72\n4.667\t84\n3.750\t75\n1.867\t51\n4.900\t82\n2.483\t62\n4.367\t88\n2.100\t49\n4.500\t83\n4.050\t81\n1.867\t47\n4.700\t84\n1.783\t52\n4.850\t86\n3.683\t81\n4.733\t75\n2.300\t59\n4.900\t89\n4.417\t79\n1.700\t59\n4.633\t81\n2.317\t50\n4.600\t85\n1.817\t59\n4.417\t87\n2.617\t53\n4.067\t69\n4.250\t77\n1.967\t56\n4.600\t88\n3.767\t81\n1.917\t45\n4.500\t82\n2.267\t55\n4.650\t90\n1.867\t45\n4.167\t83\n2.800\t56\n4.333\t89\n1.833\t46\n4.383\t82\n1.883\t51\n4.933\t86\n2.033\t53\n3.733\t79\n4.233\t81\n2.233\t60\n4.533\t82\n4.817\t77\n4.333\t76\n1.983\t59\n4.633\t80\n2.017\t49\n5.100\t96\n1.800\t53\n5.033\t77\n4.000\t77\n2.400\t65\n4.600\t81\n3.567\t71\n4.000\t70\n4.500\t81\n4.083\t93\n1.800\t53\n3.967\t89\n2.200\t45\n4.150\t86\n2.000\t58\n3.833\t78\n3.500\t66\n4.583\t76\n2.367\t63\n5.000\t88\n1.933\t52\n4.617\t93\n1.917\t49\n2.083\t57\n4.583\t77\n3.333\t68\n4.167\t81\n4.333\t81\n4.500\t73\n2.417\t50\n4.000\t85\n4.167\t74\n1.883\t55\n4.583\t77\n4.250\t83\n3.767\t83\n2.033\t51\n4.433\t78\n4.083\t84\n1.833\t46\n4.417\t83\n2.183\t55\n4.800\t81\n1.833\t57\n4.800\t76\n4.100\t84\n3.966\t77\n4.233\t81\n3.500\t87\n4.366\t77\n2.250\t51\n4.667\t78\n2.100\t60\n4.350\t82\n4.133\t91\n1.867\t53\n4.600\t78\n1.783\t46\n4.367\t77\n3.850\t84\n1.933\t49\n4.500\t83\n2.383\t71\n4.700\t80\n1.867\t49\n3.833\t75\n3.417\t64\n4.233\t76\n2.400\t53\n4.800\t94\n2.000\t55\n4.150\t76\n1.867\t50\n4.267\t82\n1.750\t54\n4.483\t75\n4.000\t78\n4.117\t79\n4.083\t78\n4.267\t78\n3.917\t70\n4.550\t79\n4.083\t70\n2.417\t54\n4.183\t86\n2.217\t50\n4.450\t90\n1.883\t54\n1.850\t54\n4.283\t77\n3.950\t79\n2.333\t64\n4.150\t75\n2.350\t47\n4.933\t86\n2.900\t63\n4.583\t85\n3.833\t82\n2.083\t57\n4.367\t82\n2.133\t67\n4.350\t74\n2.200\t54\n4.450\t83\n3.567\t73\n4.500\t73\n4.150\t88\n3.817\t80\n3.917\t71\n4.450\t83\n2.000\t56\n4.283\t79\n4.767\t78\n4.533\t84\n1.850\t58\n4.250\t83\n1.983\t43\n2.250\t60\n4.750\t75\n4.117\t81\n2.150\t46\n4.417\t90\n1.817\t46\n4.467\t74\n"
  },
  {
    "path": "test/density-test.js",
    "content": "import assert from \"assert\";\nimport {extent, ticks} from \"d3-array\";\nimport {autoType} from \"d3-dsv\";\nimport {tsv} from \"d3-fetch\";\nimport {polygonCentroid} from \"d3-polygon\";\nimport {scaleLinear} from \"d3-scale\";\nimport {contourDensity} from \"../src/index.js\";\nimport {assertInDelta} from \"./asserts.js\";\nimport it from \"./jsdom.js\";\n\nit(\"density.size(…) validates the specified size\", () => {\n  assert.deepStrictEqual(contourDensity().size([1, 2]).size(), [1, 2]);\n  assert.deepStrictEqual(contourDensity().size([0, 0]).size(), [0, 0]);\n  assert.deepStrictEqual(contourDensity().size([1.5, 2.5]).size(), [1.5, 2.5]);\n  assert.throws(() => void contourDensity().size([0, -1]), /invalid size/);\n});\n\nit(\"contourDensity(data) returns the expected result for empty data\", () => {\n  const c = contourDensity();\n  assert.deepStrictEqual(c([]), []);\n});\n\nit(\"contourDensity(data) returns contours centered on a point\", () => {\n  const c = contourDensity().thresholds([0.00001, 0.0001]);\n  for (const p of [[100, 100], [100.5, 102]]) {\n    const contour = c([p]);\n    assert.strictEqual(contour.length, 2);\n    for (const b of contour) {\n      const a = polygonCentroid(b.coordinates[0][0]);\n      assertInDelta(a[0], p[0], 0.1);\n      assertInDelta(a[1], p[1], 0.1);\n    }\n  }\n});\n\nit(\"contourDensity.thresholds(values[])(data) returns contours for the given values\", () => {\n  const points = [[1, 0], [0, 1], [1, 1]];\n  const c = contourDensity();\n  const c1 = c(points);\n  const values1 = c1.map(d => d.value);\n  const c2 = c.thresholds(values1)(points);\n  const values2 = c2.map(d => d.value);\n  assert.deepStrictEqual(values1, values2);\n});\n\nit(\"contourDensity.weight(…) accepts NaN weights\", () => {\n  const points = [[1, 0, 1], [0, 1, -2], [1, 1, NaN]];\n  const c = contourDensity().weight(d => d[2])(points);\n  assert.strictEqual(c.length, 24);\n});\n\nit(\"contourDensity.thresholds(values[])(data) returns contours for the given values at a different cellSize\", () => {\n  const points = [[1, 0], [0, 1], [1, 1]];\n  const c = contourDensity().cellSize(16);\n  const c1 = c(points);\n  const values1 = c1.map(d => d.value);\n  const c2 = c.thresholds(values1)(points);\n  const values2 = c2.map(d => d.value);\n  assert.deepStrictEqual(values1, values2);\n});\n\nit(\"contourDensity(data) returns nice default thresholds\", async () => {\n  const faithful = await tsv(\"data/faithful.tsv\", autoType);\n\n  const width = 960,\n        height = 500,\n        marginTop = 20,\n        marginRight = 30,\n        marginBottom = 30,\n        marginLeft = 40;\n\n  const x = scaleLinear()\n      .domain(extent(faithful, d => d.waiting)).nice()\n      .rangeRound([marginLeft, width - marginRight]);\n\n  const y = scaleLinear()\n      .domain(extent(faithful, d => d.eruptions)).nice()\n      .rangeRound([height - marginBottom, marginTop]);\n\n  const contour = contourDensity()\n      .x(d => x(d.waiting))\n      .y(d => y(d.eruptions))\n      .size([width, height])\n      .bandwidth(30)\n    (faithful);\n\n  assert.deepStrictEqual(contour.map(c => c.value), ticks(0.0002, 0.0059, 30));\n});\n\nit(\"contourDensity.contours(data) preserves the specified threshold exactly\", async () => {\n  const faithful = await tsv(\"data/faithful.tsv\", autoType);\n\n  const width = 960,\n        height = 500,\n        marginTop = 20,\n        marginRight = 30,\n        marginBottom = 30,\n        marginLeft = 40;\n\n  const x = scaleLinear()\n      .domain(extent(faithful, d => d.waiting)).nice()\n      .rangeRound([marginLeft, width - marginRight]);\n\n  const y = scaleLinear()\n      .domain(extent(faithful, d => d.eruptions)).nice()\n      .rangeRound([height - marginBottom, marginTop]);\n\n  const contour = contourDensity()\n      .x(d => x(d.waiting))\n      .y(d => y(d.eruptions))\n      .size([width, height])\n      .bandwidth(30)\n    .contours(faithful);\n\n  for (const value of ticks(0.0002, 0.006, 30)) {\n    assert.strictEqual(contour(value).value, value);\n  }\n});\n"
  },
  {
    "path": "test/jsdom.js",
    "content": "import {promises as fs} from \"fs\";\nimport * as path from \"path\";\nimport {JSDOM} from \"jsdom\";\n\nexport default function jsdomit(description, run) {\n  return it(description, withJsdom(run));\n}\n\njsdomit.skip = (description, run) => {\n  return it.skip(description, withJsdom(run));\n};\n\njsdomit.only = (description, run) => {\n  return it.only(description, withJsdom(run));\n};\n\nfunction withJsdom(run) {\n  return async () => {\n    const jsdom = new JSDOM(\"\");\n    global.window = jsdom.window;\n    global.document = jsdom.window.document;\n    global.navigator = jsdom.window.navigator;\n    global.Event = jsdom.window.Event;\n    global.Node = jsdom.window.Node;\n    global.NodeList = jsdom.window.NodeList;\n    global.HTMLCollection = jsdom.window.HTMLCollection;\n    global.fetch = async (href) => new Response(path.resolve(\"./test\", href));\n    try {\n      return await run();\n    } finally {\n      delete global.window;\n      delete global.document;\n      delete global.navigator;\n      delete global.Event;\n      delete global.Node;\n      delete global.NodeList;\n      delete global.HTMLCollection;\n      delete global.fetch;\n    }\n  };\n}\n\nclass Response {\n  constructor(href) {\n    this._href = href;\n    this.ok = true;\n    this.status = 200;\n  }\n  async text() {\n    return fs.readFile(this._href, {encoding: \"utf-8\"});\n  }\n  async json() {\n    return JSON.parse(await this.text());\n  }\n}\n"
  },
  {
    "path": "test/snapshot.js",
    "content": "import assert from \"assert\";\nimport {promises as fs} from \"fs\";\nimport * as path from \"path\";\nimport beautify from \"js-beautify\";\nimport it from \"./jsdom.js\";\nimport * as snapshots from \"./snapshots/index.js\";\n\nfor (const [name, snapshot] of Object.entries(snapshots)) {\n  it(`snapshot ${name}`, async () => {\n    const svg = await snapshot();\n    svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\", \"xmlns\", \"http://www.w3.org/2000/svg\");\n    svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\", \"xmlns:xlink\", \"http://www.w3.org/1999/xlink\");\n    const actual = beautify.html(svg.outerHTML, {indent_size: 2});\n    const outfile = path.resolve(\"./test/output\", `${path.basename(name, \".js\")}.svg`);\n    const diffile = path.resolve(\"./test/output\", `${path.basename(name, \".js\")}-changed.svg`);\n    let expected;\n\n    try {\n      expected = await fs.readFile(outfile, \"utf8\");\n    } catch (error) {\n      if (error.code === \"ENOENT\" && process.env.CI !== \"true\") {\n        console.warn(`! generating ${outfile}`);\n        await fs.writeFile(outfile, actual, \"utf8\");\n        return;\n      } else {\n        throw error;\n      }\n    }\n\n    if (actual === expected) {\n      if (process.env.CI !== \"true\") {\n        try {\n          await fs.unlink(diffile);\n          console.warn(`! deleted ${diffile}`);\n        } catch (error) {\n          if (error.code !== \"ENOENT\") {\n            throw error;\n          }\n        }\n      }\n    } else {\n      console.warn(`! generating ${diffile}`);\n      await fs.writeFile(diffile, actual, \"utf8\");\n    }\n\n    assert(actual === expected, `${name} must match snapshot`);\n  });\n}\n"
  },
  {
    "path": "test/snapshots/index.js",
    "content": "import {extent, ticks} from \"d3-array\";\nimport {axisBottom, axisLeft} from \"d3-axis\";\nimport {autoType} from \"d3-dsv\";\nimport {tsv} from \"d3-fetch\";\nimport {geoPath} from \"d3-geo\";\nimport {scaleLinear} from \"d3-scale\";\nimport {select} from \"d3-selection\";\nimport {svg} from \"htl\";\nimport {contourDensity} from \"d3-contour\";\n\nexport async function faithfulContours() {\n  const faithful = await tsv(\"data/faithful.tsv\", autoType);\n\n  const width = 960,\n        height = 500,\n        marginTop = 20,\n        marginRight = 30,\n        marginBottom = 30,\n        marginLeft = 40;\n\n  const x = scaleLinear()\n      .domain(extent(faithful, d => d.waiting)).nice()\n      .rangeRound([marginLeft, width - marginRight]);\n\n  const y = scaleLinear()\n      .domain(extent(faithful, d => d.eruptions)).nice()\n      .rangeRound([height - marginBottom, marginTop]);\n\n  const contours = contourDensity()\n      .x(d => x(d.waiting))\n      .y(d => y(d.eruptions))\n      .size([width, height])\n      .bandwidth(30)\n      .thresholds(30)\n    (faithful);\n\n  const path = geoPath();\n\n  return svg`<svg viewBox=\"0 0 ${width} ${height}\" width=${width} height=${height} style=\"max-width: 100%; height: auto; height: intrinsic;\">\n    ${select(svg`<g transform=\"translate(${marginLeft},0)\">`).call(axisLeft(y)).call(g => g.select(\".domain\").remove())}\n    ${select(svg`<g transform=\"translate(0,${height - marginBottom})\">`).call(axisBottom(x)).call(g => g.select(\".domain\").remove())}\n    <g>${faithful.map(d => svg`<circle cx=${x(d.waiting)} cy=${y(d.eruptions)} r=2>`)}</g>\n    <g fill=none stroke=blue>${contours.map(c => svg`<path d=${path(c)}>`)}</g>\n  </svg>`;\n}\n\nexport async function faithfulContour() {\n  const faithful = await tsv(\"data/faithful.tsv\", autoType);\n\n  const width = 960,\n        height = 500,\n        marginTop = 20,\n        marginRight = 30,\n        marginBottom = 30,\n        marginLeft = 40;\n\n  const x = scaleLinear()\n      .domain(extent(faithful, d => d.waiting)).nice()\n      .rangeRound([marginLeft, width - marginRight]);\n\n  const y = scaleLinear()\n      .domain(extent(faithful, d => d.eruptions)).nice()\n      .rangeRound([height - marginBottom, marginTop]);\n\n  const contour = contourDensity()\n      .x(d => x(d.waiting))\n      .y(d => y(d.eruptions))\n      .size([width, height])\n      .bandwidth(30)\n    .contours(faithful);\n\n  const thresholds = ticks(0, contour.max, 30).slice(1);\n\n  const path = geoPath();\n\n  return svg`<svg viewBox=\"0 0 ${width} ${height}\" width=${width} height=${height} style=\"max-width: 100%; height: auto; height: intrinsic;\">\n    ${select(svg`<g transform=\"translate(${marginLeft},0)\">`).call(axisLeft(y)).call(g => g.select(\".domain\").remove())}\n    ${select(svg`<g transform=\"translate(0,${height - marginBottom})\">`).call(axisBottom(x)).call(g => g.select(\".domain\").remove())}\n    <g>${faithful.map(d => svg`<circle cx=${x(d.waiting)} cy=${y(d.eruptions)} r=2>`)}</g>\n    <g fill=none stroke=red>${thresholds.map(t => svg`<path d=${path(contour(t))}>`)}</g>\n  </svg>`;\n}\n"
  }
]