Full Code of d3/d3-contour for AI

main 8a3b95a65a46 cached
25 files
41.7 KB
17.8k tokens
35 symbols
1 requests
Download .txt
Repository: d3/d3-contour
Branch: main
Commit: 8a3b95a65a46
Files: 25
Total size: 41.7 KB

Directory structure:
gitextract_9aal4xuq/

├── .eslintrc.json
├── .github/
│   ├── eslint.json
│   └── workflows/
│       └── node.js.yml
├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src/
│   ├── area.js
│   ├── array.js
│   ├── ascending.js
│   ├── constant.js
│   ├── contains.js
│   ├── contours.js
│   ├── density.js
│   ├── index.js
│   └── noop.js
└── test/
    ├── .eslintrc.json
    ├── asserts.js
    ├── contours-test.js
    ├── data/
    │   └── faithful.tsv
    ├── density-test.js
    ├── jsdom.js
    ├── snapshot.js
    └── snapshots/
        └── index.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.json
================================================
{
  "extends": "eslint:recommended",
  "parserOptions": {
    "sourceType": "module",
    "ecmaVersion": 2020
  },
  "env": {
    "es6": true,
    "node": true,
    "browser": true
  },
  "rules": {
    "no-cond-assign": 0,
    "no-constant-condition": 0,
    "no-sparse-arrays": 0,
    "no-unexpected-multiline": 0,
    "comma-dangle": ["error", "never"],
    "semi": [2, "always"]
  }
}


================================================
FILE: .github/eslint.json
================================================
{
  "problemMatcher": [
    {
      "owner": "eslint-compact",
      "pattern": [
        {
          "regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s(Error|Warning|Info)\\s-\\s(.+)\\s\\((.+)\\)$",
          "file": 1,
          "line": 2,
          "column": 3,
          "severity": 4,
          "message": 5,
          "code": 6
        }
      ]
    }
  ]
}


================================================
FILE: .github/workflows/node.js.yml
================================================
# https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: yarn --frozen-lockfile
    - run: |
        echo ::add-matcher::.github/eslint.json
        yarn run eslint src test --format=compact
    - run: yarn test


================================================
FILE: .gitignore
================================================
*.sublime-workspace
.DS_Store
dist/
node_modules
npm-debug.log


================================================
FILE: LICENSE
================================================
Copyright 2012-2023 Mike Bostock

Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.


================================================
FILE: README.md
================================================
# d3-contour

<a href="https://d3js.org"><img src="https://github.com/d3/d3/raw/main/docs/public/logo.svg" width="256" height="256"></a>

This module computes contour polygons by applying [marching squares](https://en.wikipedia.org/wiki/Marching_squares) to a rectangular array of numeric values.

## Resources

- [Documentation](https://d3js.org/d3-contour)
- [Examples](https://observablehq.com/collection/@d3/d3-contour)
- [Releases](https://github.com/d3/d3-contour/releases)
- [Getting help](https://d3js.org/community)


================================================
FILE: package.json
================================================
{
  "name": "d3-contour",
  "version": "4.0.2",
  "description": "Compute contour polygons using marching squares.",
  "homepage": "https://d3js.org/d3-contour/",
  "repository": {
    "type": "git",
    "url": "https://github.com/d3/d3-contour.git"
  },
  "keywords": [
    "d3",
    "d3-module",
    "contour",
    "isoline"
  ],
  "license": "ISC",
  "author": {
    "name": "Mike Bostock",
    "url": "http://bost.ocks.org/mike"
  },
  "type": "module",
  "files": [
    "dist/**/*.js",
    "src/**/*.js"
  ],
  "module": "src/index.js",
  "main": "src/index.js",
  "jsdelivr": "dist/d3-contour.min.js",
  "unpkg": "dist/d3-contour.min.js",
  "exports": {
    "umd": "./dist/d3-contour.min.js",
    "default": "./src/index.js"
  },
  "_moduleAliases": {
    "d3-contour": "./src/index.js"
  },
  "sideEffects": false,
  "dependencies": {
    "d3-array": "^3.2.0"
  },
  "devDependencies": {
    "d3-axis": "3",
    "d3-dsv": "3",
    "d3-fetch": "3",
    "d3-geo": "3",
    "d3-polygon": "3",
    "d3-scale": "4",
    "d3-selection": "3",
    "eslint": "8",
    "htl": "^0.3.1",
    "js-beautify": "1",
    "jsdom": "20",
    "mocha": "10",
    "module-alias": "2",
    "rollup": "3",
    "rollup-plugin-terser": "7"
  },
  "scripts": {
    "test": "mkdir -p test/output && mocha -r module-alias/register 'test/**/*-test.js' test/snapshot.js && eslint src test",
    "prepublishOnly": "rm -rf dist && rollup -c",
    "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 -"
  },
  "engines": {
    "node": ">=12"
  }
}


================================================
FILE: rollup.config.js
================================================
import {readFileSync} from "fs";
import {terser} from "rollup-plugin-terser";
import meta from "./package.json" assert {type: "json"};

// Extract copyrights from the LICENSE.
const copyright = readFileSync("./LICENSE", "utf-8")
  .split(/\n/g)
  .filter(line => /^Copyright\s+/.test(line))
  .map(line => line.replace(/^Copyright\s+/, ""))
  .join(", ");

const config = {
  input: "src/index.js",
  external: Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)),
  output: {
    file: `dist/${meta.name}.js`,
    name: "d3",
    format: "umd",
    indent: false,
    extend: true,
    banner: `// ${meta.homepage} v${meta.version} Copyright ${copyright}`,
    globals: Object.assign({}, ...Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)).map(key => ({[key]: "d3"})))
  },
  plugins: []
};

export default [
  config,
  {
    ...config,
    output: {
      ...config.output,
      file: `dist/${meta.name}.min.js`
    },
    plugins: [
      ...config.plugins,
      terser({
        output: {
          preamble: config.output.banner
        }
      })
    ]
  }
];


================================================
FILE: src/area.js
================================================
export default function(ring) {
  var i = 0, n = ring.length, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];
  while (++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];
  return area;
}


================================================
FILE: src/array.js
================================================
var array = Array.prototype;

export var slice = array.slice;


================================================
FILE: src/ascending.js
================================================
export default function(a, b) {
  return a - b;
}


================================================
FILE: src/constant.js
================================================
export default x => () => x;


================================================
FILE: src/contains.js
================================================
export default function(ring, hole) {
  var i = -1, n = hole.length, c;
  while (++i < n) if (c = ringContains(ring, hole[i])) return c;
  return 0;
}

function ringContains(ring, point) {
  var x = point[0], y = point[1], contains = -1;
  for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {
    var pi = ring[i], xi = pi[0], yi = pi[1], pj = ring[j], xj = pj[0], yj = pj[1];
    if (segmentContains(pi, pj, point)) return 0;
    if (((yi > y) !== (yj > y)) && ((x < (xj - xi) * (y - yi) / (yj - yi) + xi))) contains = -contains;
  }
  return contains;
}

function segmentContains(a, b, c) {
  var i; return collinear(a, b, c) && within(a[i = +(a[0] === b[0])], c[i], b[i]);
}

function collinear(a, b, c) {
  return (b[0] - a[0]) * (c[1] - a[1]) === (c[0] - a[0]) * (b[1] - a[1]);
}

function within(p, q, r) {
  return p <= q && q <= r || r <= q && q <= p;
}


================================================
FILE: src/contours.js
================================================
import {extent, nice, thresholdSturges, ticks} from "d3-array";
import {slice} from "./array.js";
import ascending from "./ascending.js";
import area from "./area.js";
import constant from "./constant.js";
import contains from "./contains.js";
import noop from "./noop.js";

var cases = [
  [],
  [[[1.0, 1.5], [0.5, 1.0]]],
  [[[1.5, 1.0], [1.0, 1.5]]],
  [[[1.5, 1.0], [0.5, 1.0]]],
  [[[1.0, 0.5], [1.5, 1.0]]],
  [[[1.0, 1.5], [0.5, 1.0]], [[1.0, 0.5], [1.5, 1.0]]],
  [[[1.0, 0.5], [1.0, 1.5]]],
  [[[1.0, 0.5], [0.5, 1.0]]],
  [[[0.5, 1.0], [1.0, 0.5]]],
  [[[1.0, 1.5], [1.0, 0.5]]],
  [[[0.5, 1.0], [1.0, 0.5]], [[1.5, 1.0], [1.0, 1.5]]],
  [[[1.5, 1.0], [1.0, 0.5]]],
  [[[0.5, 1.0], [1.5, 1.0]]],
  [[[1.0, 1.5], [1.5, 1.0]]],
  [[[0.5, 1.0], [1.0, 1.5]]],
  []
];

export default function() {
  var dx = 1,
      dy = 1,
      threshold = thresholdSturges,
      smooth = smoothLinear;

  function contours(values) {
    var tz = threshold(values);

    // Convert number of thresholds into uniform thresholds.
    if (!Array.isArray(tz)) {
      const e = extent(values, finite);
      tz = ticks(...nice(e[0], e[1], tz), tz);
      while (tz[tz.length - 1] >= e[1]) tz.pop();
      while (tz[1] < e[0]) tz.shift();
    } else {
      tz = tz.slice().sort(ascending);
    }

    return tz.map(value => contour(values, value));
  }

  // Accumulate, smooth contour rings, assign holes to exterior rings.
  // Based on https://github.com/mbostock/shapefile/blob/v0.6.2/shp/polygon.js
  function contour(values, value) {
    const v = value == null ? NaN : +value;
    if (isNaN(v)) throw new Error(`invalid value: ${value}`);

    var polygons = [],
        holes = [];

    isorings(values, v, function(ring) {
      smooth(ring, values, v);
      if (area(ring) > 0) polygons.push([ring]);
      else holes.push(ring);
    });

    holes.forEach(function(hole) {
      for (var i = 0, n = polygons.length, polygon; i < n; ++i) {
        if (contains((polygon = polygons[i])[0], hole) !== -1) {
          polygon.push(hole);
          return;
        }
      }
    });

    return {
      type: "MultiPolygon",
      value: value,
      coordinates: polygons
    };
  }

  // Marching squares with isolines stitched into rings.
  // Based on https://github.com/topojson/topojson-client/blob/v3.0.0/src/stitch.js
  function isorings(values, value, callback) {
    var fragmentByStart = new Array,
        fragmentByEnd = new Array,
        x, y, t0, t1, t2, t3;

    // Special case for the first row (y = -1, t2 = t3 = 0).
    x = y = -1;
    t1 = above(values[0], value);
    cases[t1 << 1].forEach(stitch);
    while (++x < dx - 1) {
      t0 = t1, t1 = above(values[x + 1], value);
      cases[t0 | t1 << 1].forEach(stitch);
    }
    cases[t1 << 0].forEach(stitch);

    // General case for the intermediate rows.
    while (++y < dy - 1) {
      x = -1;
      t1 = above(values[y * dx + dx], value);
      t2 = above(values[y * dx], value);
      cases[t1 << 1 | t2 << 2].forEach(stitch);
      while (++x < dx - 1) {
        t0 = t1, t1 = above(values[y * dx + dx + x + 1], value);
        t3 = t2, t2 = above(values[y * dx + x + 1], value);
        cases[t0 | t1 << 1 | t2 << 2 | t3 << 3].forEach(stitch);
      }
      cases[t1 | t2 << 3].forEach(stitch);
    }

    // Special case for the last row (y = dy - 1, t0 = t1 = 0).
    x = -1;
    t2 = values[y * dx] >= value;
    cases[t2 << 2].forEach(stitch);
    while (++x < dx - 1) {
      t3 = t2, t2 = above(values[y * dx + x + 1], value);
      cases[t2 << 2 | t3 << 3].forEach(stitch);
    }
    cases[t2 << 3].forEach(stitch);

    function stitch(line) {
      var start = [line[0][0] + x, line[0][1] + y],
          end = [line[1][0] + x, line[1][1] + y],
          startIndex = index(start),
          endIndex = index(end),
          f, g;
      if (f = fragmentByEnd[startIndex]) {
        if (g = fragmentByStart[endIndex]) {
          delete fragmentByEnd[f.end];
          delete fragmentByStart[g.start];
          if (f === g) {
            f.ring.push(end);
            callback(f.ring);
          } else {
            fragmentByStart[f.start] = fragmentByEnd[g.end] = {start: f.start, end: g.end, ring: f.ring.concat(g.ring)};
          }
        } else {
          delete fragmentByEnd[f.end];
          f.ring.push(end);
          fragmentByEnd[f.end = endIndex] = f;
        }
      } else if (f = fragmentByStart[endIndex]) {
        if (g = fragmentByEnd[startIndex]) {
          delete fragmentByStart[f.start];
          delete fragmentByEnd[g.end];
          if (f === g) {
            f.ring.push(end);
            callback(f.ring);
          } else {
            fragmentByStart[g.start] = fragmentByEnd[f.end] = {start: g.start, end: f.end, ring: g.ring.concat(f.ring)};
          }
        } else {
          delete fragmentByStart[f.start];
          f.ring.unshift(start);
          fragmentByStart[f.start = startIndex] = f;
        }
      } else {
        fragmentByStart[startIndex] = fragmentByEnd[endIndex] = {start: startIndex, end: endIndex, ring: [start, end]};
      }
    }
  }

  function index(point) {
    return point[0] * 2 + point[1] * (dx + 1) * 4;
  }

  function smoothLinear(ring, values, value) {
    ring.forEach(function(point) {
      var x = point[0],
          y = point[1],
          xt = x | 0,
          yt = y | 0,
          v1 = valid(values[yt * dx + xt]);
      if (x > 0 && x < dx && xt === x) {
        point[0] = smooth1(x, valid(values[yt * dx + xt - 1]), v1, value);
      }
      if (y > 0 && y < dy && yt === y) {
        point[1] = smooth1(y, valid(values[(yt - 1) * dx + xt]), v1, value);
      }
    });
  }

  contours.contour = contour;

  contours.size = function(_) {
    if (!arguments.length) return [dx, dy];
    var _0 = Math.floor(_[0]), _1 = Math.floor(_[1]);
    if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
    return dx = _0, dy = _1, contours;
  };

  contours.thresholds = function(_) {
    return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), contours) : threshold;
  };

  contours.smooth = function(_) {
    return arguments.length ? (smooth = _ ? smoothLinear : noop, contours) : smooth === smoothLinear;
  };

  return contours;
}

// When computing the extent, ignore infinite values (as well as invalid ones).
function finite(x) {
  return isFinite(x) ? x : NaN;
}

// Is the (possibly invalid) x greater than or equal to the (known valid) value?
// Treat any invalid value as below negative infinity.
function above(x, value) {
  return x == null ? false : +x >= value;
}

// During smoothing, treat any invalid value as negative infinity.
function valid(v) {
  return v == null || isNaN(v = +v) ? -Infinity : v;
}

function smooth1(x, v0, v1, value) {
  const a = value - v0;
  const b = v1 - v0;
  const d = isFinite(a) || isFinite(b) ? a / b : Math.sign(a) / Math.sign(b);
  return isNaN(d) ? x : x + d - 0.5;
}


================================================
FILE: src/density.js
================================================
import {blur2, max, ticks} from "d3-array";
import {slice} from "./array.js";
import constant from "./constant.js";
import Contours from "./contours.js";

function defaultX(d) {
  return d[0];
}

function defaultY(d) {
  return d[1];
}

function defaultWeight() {
  return 1;
}

export default function() {
  var x = defaultX,
      y = defaultY,
      weight = defaultWeight,
      dx = 960,
      dy = 500,
      r = 20, // blur radius
      k = 2, // log2(grid cell size)
      o = r * 3, // grid offset, to pad for blur
      n = (dx + o * 2) >> k, // grid width
      m = (dy + o * 2) >> k, // grid height
      threshold = constant(20);

  function grid(data) {
    var values = new Float32Array(n * m),
        pow2k = Math.pow(2, -k),
        i = -1;

    for (const d of data) {
      var xi = (x(d, ++i, data) + o) * pow2k,
          yi = (y(d, i, data) + o) * pow2k,
          wi = +weight(d, i, data);
      if (wi && xi >= 0 && xi < n && yi >= 0 && yi < m) {
        var x0 = Math.floor(xi),
            y0 = Math.floor(yi),
            xt = xi - x0 - 0.5,
            yt = yi - y0 - 0.5;
        values[x0 + y0 * n] += (1 - xt) * (1 - yt) * wi;
        values[x0 + 1 + y0 * n] += xt * (1 - yt) * wi;
        values[x0 + 1 + (y0 + 1) * n] += xt * yt * wi;
        values[x0 + (y0 + 1) * n] += (1 - xt) * yt * wi;
      }
    }

    blur2({data: values, width: n, height: m}, r * pow2k);
    return values;
  }

  function density(data) {
    var values = grid(data),
        tz = threshold(values),
        pow4k = Math.pow(2, 2 * k);

    // Convert number of thresholds into uniform thresholds.
    if (!Array.isArray(tz)) {
      tz = ticks(Number.MIN_VALUE, max(values) / pow4k, tz);
    }

    return Contours()
        .size([n, m])
        .thresholds(tz.map(d => d * pow4k))
      (values)
        .map((c, i) => (c.value = +tz[i], transform(c)));
  }

  density.contours = function(data) {
    var values = grid(data),
        contours = Contours().size([n, m]),
        pow4k = Math.pow(2, 2 * k),
        contour = value => {
          value = +value;
          var c = transform(contours.contour(values, value * pow4k));
          c.value = value; // preserve exact threshold value
          return c;
        };
    Object.defineProperty(contour, "max", {get: () => max(values) / pow4k});
    return contour;
  };

  function transform(geometry) {
    geometry.coordinates.forEach(transformPolygon);
    return geometry;
  }

  function transformPolygon(coordinates) {
    coordinates.forEach(transformRing);
  }

  function transformRing(coordinates) {
    coordinates.forEach(transformPoint);
  }

  // TODO Optimize.
  function transformPoint(coordinates) {
    coordinates[0] = coordinates[0] * Math.pow(2, k) - o;
    coordinates[1] = coordinates[1] * Math.pow(2, k) - o;
  }

  function resize() {
    o = r * 3;
    n = (dx + o * 2) >> k;
    m = (dy + o * 2) >> k;
    return density;
  }

  density.x = function(_) {
    return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), density) : x;
  };

  density.y = function(_) {
    return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), density) : y;
  };

  density.weight = function(_) {
    return arguments.length ? (weight = typeof _ === "function" ? _ : constant(+_), density) : weight;
  };

  density.size = function(_) {
    if (!arguments.length) return [dx, dy];
    var _0 = +_[0], _1 = +_[1];
    if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
    return dx = _0, dy = _1, resize();
  };

  density.cellSize = function(_) {
    if (!arguments.length) return 1 << k;
    if (!((_ = +_) >= 1)) throw new Error("invalid cell size");
    return k = Math.floor(Math.log(_) / Math.LN2), resize();
  };

  density.thresholds = function(_) {
    return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), density) : threshold;
  };

  density.bandwidth = function(_) {
    if (!arguments.length) return Math.sqrt(r * (r + 1));
    if (!((_ = +_) >= 0)) throw new Error("invalid bandwidth");
    return r = (Math.sqrt(4 * _ * _ + 1) - 1) / 2, resize();
  };

  return density;
}


================================================
FILE: src/index.js
================================================
export {default as contours} from "./contours.js";
export {default as contourDensity} from "./density.js";


================================================
FILE: src/noop.js
================================================
export default function() {}


================================================
FILE: test/.eslintrc.json
================================================
{
  "env": {
    "mocha": true
  }
}


================================================
FILE: test/asserts.js
================================================
import assert from "assert";

export function assertInDelta(actual, expected, delta = 1e-6) {
  assert(inDelta(actual, expected, delta));
}

function inDelta(actual, expected, delta) {
  return (Array.isArray(expected) ? inDeltaArray : inDeltaNumber)(actual, expected, delta);
}

function inDeltaArray(actual, expected, delta) {
  const n = expected.length;
  if (actual.length !== n) return false;
  for (let i = 0; i < n; ++i) if (!inDelta(actual[i], expected[i], delta)) return false;
  return true;
}

function inDeltaNumber(actual, expected, delta) {
  return actual >= expected - delta && actual <= expected + delta;
}


================================================
FILE: test/contours-test.js
================================================
import assert from "assert";
import {contours} from "../src/index.js";

it("contours(values) returns the expected result for an empty polygon", () => {
  const c = contours().size([10, 10]).thresholds([0.5]);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]), [
    {
      "type": "MultiPolygon",
      "value": 0.5,
      "coordinates": []
    }
  ]);
});

it("contours(values) returns the expected result for a simple polygon", () => {
  const c = contours().size([10, 10]).thresholds([0.5]);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]), [
    {
      "type": "MultiPolygon",
      "value": 0.5,
      "coordinates": [
        [
          [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],
           [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],
           [4.5, 8], [5.5, 8], [6, 7.5]]
        ]
      ]
    }
  ]);
});

it("contours(values).contour(value) returns the expected result for a simple polygon", () => {
  const c = contours().size([10, 10]);
  assert.deepStrictEqual(c.contour([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ], 0.5), {
    "type": "MultiPolygon",
    "value": 0.5,
    "coordinates": [
      [
        [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],
         [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],
         [4.5, 8], [5.5, 8], [6, 7.5]]
      ]
    ]
  });
});

it("contours.smooth(false)(values) returns the expected result for a simple polygon", () => {
  const c = contours().smooth(false).size([10, 10]).thresholds([0.5]);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 2, 1, 2, 0, 0, 0, 0,
    0, 0, 0, 2, 2, 2, 0, 0, 0, 0,
    0, 0, 0, 1, 2, 1, 0, 0, 0, 0,
    0, 0, 0, 2, 2, 2, 0, 0, 0, 0,
    0, 0, 0, 2, 1, 2, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]), [
    {
      "type": "MultiPolygon",
      "value": 0.5,
      "coordinates": [
        [
          [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],
           [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],
           [4.5, 8], [5.5, 8], [6, 7.5]]
        ]
      ]
    }
  ]);
});

it("contours(values) returns the expected result for a polygon with a hole", () => {
  const c = contours().size([10, 10]).thresholds([0.5]);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]), [
    {
      "type": "MultiPolygon",
      "value": 0.5,
      "coordinates": [
        [
          [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],
           [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],
           [4.5, 8], [5.5, 8], [6, 7.5]],
          [[4.5, 7], [4, 6.5], [4, 5.5], [4, 4.5], [4.5, 4], [5, 4.5], [5, 5.5],
           [5, 6.5], [4.5, 7]]
        ]
      ]
    }
  ]);
});

it("contours(values) returns the expected result for a multipolygon", () => {
  const c = contours().size([10, 10]).thresholds([0.5]);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
    0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]), [
    {
      "type": "MultiPolygon",
      "value": 0.5,
      "coordinates": [
        [
          [[5, 7.5], [5, 6.5], [5, 5.5], [5, 4.5], [5, 3.5], [4.5, 3], [3.5, 3],
           [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8], [4.5, 8],
           [5, 7.5]]
        ],
        [
          [[7, 7.5], [7, 6.5], [7, 5.5], [7, 4.5], [7, 3.5], [6.5, 3], [6, 3.5],
           [6, 4.5], [6, 5.5], [6, 6.5], [6, 7.5], [6.5, 8], [7, 7.5]]
        ]
      ]
    }
  ]);
});

it("contours(values) returns the expected result for a multipolygon with holes", () => {
  const c = contours().size([10, 10]).thresholds([0.5]);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
    0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]), [
    {
      "type": "MultiPolygon",
      "value": 0.5,
      "coordinates": [
        [
          [[4, 5.5], [4, 4.5], [4, 3.5], [3.5, 3], [2.5, 3], [1.5, 3], [1, 3.5],
           [1, 4.5], [1, 5.5], [1.5, 6], [2.5, 6], [3.5, 6], [4, 5.5]],
          [[2.5, 5], [2, 4.5], [2.5, 4], [3, 4.5], [2.5, 5]]
        ],
        [
          [[8, 5.5], [8, 4.5], [8, 3.5], [7.5, 3], [6.5, 3], [5.5, 3], [5, 3.5],
           [5, 4.5], [5, 5.5], [5.5, 6], [6.5, 6], [7.5, 6], [8, 5.5]],
          [[6.5, 5], [6, 4.5], [6.5, 4], [7, 4.5], [6.5, 5]]
        ]
      ]
    }
  ]);
});

it("contours.size(…) validates the specified size", () => {
  assert.deepStrictEqual(contours().size([1, 2]).size(), [1, 2]);
  assert.deepStrictEqual(contours().size([0, 0]).size(), [0, 0]);
  assert.deepStrictEqual(contours().size([1.5, 2.5]).size(), [1, 2]);
  assert.throws(() => void contours().size([0, -1]), /invalid size/);
});

it("contours(values) returns the expected thresholds", () => {
  const c = contours().size([10, 10]).thresholds(20);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
    0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]).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]);
});

it("contours(values) ignores infinite values when computing the thresholds", () => {
  const c = contours().size([10, 10]).thresholds(20);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, -Infinity, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
    0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
    0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, Infinity, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]).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]);
});

it("contours(values) treats null, undefined, NaN and -Infinity as holes", () => {
  const c = contours().size([10, 10]);
  assert.deepStrictEqual(c.contour([
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, -Infinity, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, null, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 2, 2, 2, 1,
    1, 1, NaN, 1, 1, 1, 2, -Infinity, 2, 1,
    1, 1, 1, 1, 1, 1, 2, 2, 2, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1
  ], 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]]]]});
});

it("contours(values) returns the expected result for a +Infinity value", () => {
  const c = contours().size([10, 10]).thresholds([0.5]);
  assert.deepStrictEqual(c([
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, +Infinity, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, +Infinity, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ]), [
    {
      "type": "MultiPolygon",
      "value": 0.5,
      "coordinates": [
        [
          [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],
           [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],
           [4.5, 8], [5.5, 8], [6, 7.5]]
        ]
      ]
    }
  ]);
});

it("contour(values, invalid value) throws an error", () => {
  for (const value of [NaN, null, undefined, "a string"]) {
    assert.throws(() => contours().size([3, 3]).contour([1, 2, 3, 4, 5, 6, 7, 8, 9], value),  /invalid value/);
  }
});

it("contours(values) uses the expected nice thresholds", () => {
  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]);
  assert.deepStrictEqual(contours().size([2, 1]).thresholds(5)([-149.76192742819748, 321.19300631539585]).map((c) => c.value), [-200, -100, 0, 100, 200, 300]);
  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]);
  assert.deepStrictEqual(contours().size([2, 1]).thresholds(5)([149.76192742819748, -321.19300631539585]).map((c) => c.value), [-400, -300, -200, -100, 0, 100]);
  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]);
  assert.deepStrictEqual(contours().size([2, 1]).thresholds(10)([-41, 245]).map((c) => c.value), [-50, 0, 50, 100, 150, 200]);
  assert.deepStrictEqual(contours().size([2, 1]).thresholds(9)([-22, 242]).map((c) => c.value), [-50, 0, 50, 100, 150, 200]);
});


================================================
FILE: test/data/faithful.tsv
================================================
eruptions	waiting
3.600	79
1.800	54
3.333	74
2.283	62
4.533	85
2.883	55
4.700	88
3.600	85
1.950	51
4.350	85
1.833	54
3.917	84
4.200	78
1.750	47
4.700	83
2.167	52
1.750	62
4.800	84
1.600	52
4.250	79
1.800	51
1.750	47
3.450	78
3.067	69
4.533	74
3.600	83
1.967	55
4.083	76
3.850	78
4.433	79
4.300	73
4.467	77
3.367	66
4.033	80
3.833	74
2.017	52
1.867	48
4.833	80
1.833	59
4.783	90
4.350	80
1.883	58
4.567	84
1.750	58
4.533	73
3.317	83
3.833	64
2.100	53
4.633	82
2.000	59
4.800	75
4.716	90
1.833	54
4.833	80
1.733	54
4.883	83
3.717	71
1.667	64
4.567	77
4.317	81
2.233	59
4.500	84
1.750	48
4.800	82
1.817	60
4.400	92
4.167	78
4.700	78
2.067	65
4.700	73
4.033	82
1.967	56
4.500	79
4.000	71
1.983	62
5.067	76
2.017	60
4.567	78
3.883	76
3.600	83
4.133	75
4.333	82
4.100	70
2.633	65
4.067	73
4.933	88
3.950	76
4.517	80
2.167	48
4.000	86
2.200	60
4.333	90
1.867	50
4.817	78
1.833	63
4.300	72
4.667	84
3.750	75
1.867	51
4.900	82
2.483	62
4.367	88
2.100	49
4.500	83
4.050	81
1.867	47
4.700	84
1.783	52
4.850	86
3.683	81
4.733	75
2.300	59
4.900	89
4.417	79
1.700	59
4.633	81
2.317	50
4.600	85
1.817	59
4.417	87
2.617	53
4.067	69
4.250	77
1.967	56
4.600	88
3.767	81
1.917	45
4.500	82
2.267	55
4.650	90
1.867	45
4.167	83
2.800	56
4.333	89
1.833	46
4.383	82
1.883	51
4.933	86
2.033	53
3.733	79
4.233	81
2.233	60
4.533	82
4.817	77
4.333	76
1.983	59
4.633	80
2.017	49
5.100	96
1.800	53
5.033	77
4.000	77
2.400	65
4.600	81
3.567	71
4.000	70
4.500	81
4.083	93
1.800	53
3.967	89
2.200	45
4.150	86
2.000	58
3.833	78
3.500	66
4.583	76
2.367	63
5.000	88
1.933	52
4.617	93
1.917	49
2.083	57
4.583	77
3.333	68
4.167	81
4.333	81
4.500	73
2.417	50
4.000	85
4.167	74
1.883	55
4.583	77
4.250	83
3.767	83
2.033	51
4.433	78
4.083	84
1.833	46
4.417	83
2.183	55
4.800	81
1.833	57
4.800	76
4.100	84
3.966	77
4.233	81
3.500	87
4.366	77
2.250	51
4.667	78
2.100	60
4.350	82
4.133	91
1.867	53
4.600	78
1.783	46
4.367	77
3.850	84
1.933	49
4.500	83
2.383	71
4.700	80
1.867	49
3.833	75
3.417	64
4.233	76
2.400	53
4.800	94
2.000	55
4.150	76
1.867	50
4.267	82
1.750	54
4.483	75
4.000	78
4.117	79
4.083	78
4.267	78
3.917	70
4.550	79
4.083	70
2.417	54
4.183	86
2.217	50
4.450	90
1.883	54
1.850	54
4.283	77
3.950	79
2.333	64
4.150	75
2.350	47
4.933	86
2.900	63
4.583	85
3.833	82
2.083	57
4.367	82
2.133	67
4.350	74
2.200	54
4.450	83
3.567	73
4.500	73
4.150	88
3.817	80
3.917	71
4.450	83
2.000	56
4.283	79
4.767	78
4.533	84
1.850	58
4.250	83
1.983	43
2.250	60
4.750	75
4.117	81
2.150	46
4.417	90
1.817	46
4.467	74


================================================
FILE: test/density-test.js
================================================
import assert from "assert";
import {extent, ticks} from "d3-array";
import {autoType} from "d3-dsv";
import {tsv} from "d3-fetch";
import {polygonCentroid} from "d3-polygon";
import {scaleLinear} from "d3-scale";
import {contourDensity} from "../src/index.js";
import {assertInDelta} from "./asserts.js";
import it from "./jsdom.js";

it("density.size(…) validates the specified size", () => {
  assert.deepStrictEqual(contourDensity().size([1, 2]).size(), [1, 2]);
  assert.deepStrictEqual(contourDensity().size([0, 0]).size(), [0, 0]);
  assert.deepStrictEqual(contourDensity().size([1.5, 2.5]).size(), [1.5, 2.5]);
  assert.throws(() => void contourDensity().size([0, -1]), /invalid size/);
});

it("contourDensity(data) returns the expected result for empty data", () => {
  const c = contourDensity();
  assert.deepStrictEqual(c([]), []);
});

it("contourDensity(data) returns contours centered on a point", () => {
  const c = contourDensity().thresholds([0.00001, 0.0001]);
  for (const p of [[100, 100], [100.5, 102]]) {
    const contour = c([p]);
    assert.strictEqual(contour.length, 2);
    for (const b of contour) {
      const a = polygonCentroid(b.coordinates[0][0]);
      assertInDelta(a[0], p[0], 0.1);
      assertInDelta(a[1], p[1], 0.1);
    }
  }
});

it("contourDensity.thresholds(values[])(data) returns contours for the given values", () => {
  const points = [[1, 0], [0, 1], [1, 1]];
  const c = contourDensity();
  const c1 = c(points);
  const values1 = c1.map(d => d.value);
  const c2 = c.thresholds(values1)(points);
  const values2 = c2.map(d => d.value);
  assert.deepStrictEqual(values1, values2);
});

it("contourDensity.weight(…) accepts NaN weights", () => {
  const points = [[1, 0, 1], [0, 1, -2], [1, 1, NaN]];
  const c = contourDensity().weight(d => d[2])(points);
  assert.strictEqual(c.length, 24);
});

it("contourDensity.thresholds(values[])(data) returns contours for the given values at a different cellSize", () => {
  const points = [[1, 0], [0, 1], [1, 1]];
  const c = contourDensity().cellSize(16);
  const c1 = c(points);
  const values1 = c1.map(d => d.value);
  const c2 = c.thresholds(values1)(points);
  const values2 = c2.map(d => d.value);
  assert.deepStrictEqual(values1, values2);
});

it("contourDensity(data) returns nice default thresholds", async () => {
  const faithful = await tsv("data/faithful.tsv", autoType);

  const width = 960,
        height = 500,
        marginTop = 20,
        marginRight = 30,
        marginBottom = 30,
        marginLeft = 40;

  const x = scaleLinear()
      .domain(extent(faithful, d => d.waiting)).nice()
      .rangeRound([marginLeft, width - marginRight]);

  const y = scaleLinear()
      .domain(extent(faithful, d => d.eruptions)).nice()
      .rangeRound([height - marginBottom, marginTop]);

  const contour = contourDensity()
      .x(d => x(d.waiting))
      .y(d => y(d.eruptions))
      .size([width, height])
      .bandwidth(30)
    (faithful);

  assert.deepStrictEqual(contour.map(c => c.value), ticks(0.0002, 0.0059, 30));
});

it("contourDensity.contours(data) preserves the specified threshold exactly", async () => {
  const faithful = await tsv("data/faithful.tsv", autoType);

  const width = 960,
        height = 500,
        marginTop = 20,
        marginRight = 30,
        marginBottom = 30,
        marginLeft = 40;

  const x = scaleLinear()
      .domain(extent(faithful, d => d.waiting)).nice()
      .rangeRound([marginLeft, width - marginRight]);

  const y = scaleLinear()
      .domain(extent(faithful, d => d.eruptions)).nice()
      .rangeRound([height - marginBottom, marginTop]);

  const contour = contourDensity()
      .x(d => x(d.waiting))
      .y(d => y(d.eruptions))
      .size([width, height])
      .bandwidth(30)
    .contours(faithful);

  for (const value of ticks(0.0002, 0.006, 30)) {
    assert.strictEqual(contour(value).value, value);
  }
});


================================================
FILE: test/jsdom.js
================================================
import {promises as fs} from "fs";
import * as path from "path";
import {JSDOM} from "jsdom";

export default function jsdomit(description, run) {
  return it(description, withJsdom(run));
}

jsdomit.skip = (description, run) => {
  return it.skip(description, withJsdom(run));
};

jsdomit.only = (description, run) => {
  return it.only(description, withJsdom(run));
};

function withJsdom(run) {
  return async () => {
    const jsdom = new JSDOM("");
    global.window = jsdom.window;
    global.document = jsdom.window.document;
    global.navigator = jsdom.window.navigator;
    global.Event = jsdom.window.Event;
    global.Node = jsdom.window.Node;
    global.NodeList = jsdom.window.NodeList;
    global.HTMLCollection = jsdom.window.HTMLCollection;
    global.fetch = async (href) => new Response(path.resolve("./test", href));
    try {
      return await run();
    } finally {
      delete global.window;
      delete global.document;
      delete global.navigator;
      delete global.Event;
      delete global.Node;
      delete global.NodeList;
      delete global.HTMLCollection;
      delete global.fetch;
    }
  };
}

class Response {
  constructor(href) {
    this._href = href;
    this.ok = true;
    this.status = 200;
  }
  async text() {
    return fs.readFile(this._href, {encoding: "utf-8"});
  }
  async json() {
    return JSON.parse(await this.text());
  }
}


================================================
FILE: test/snapshot.js
================================================
import assert from "assert";
import {promises as fs} from "fs";
import * as path from "path";
import beautify from "js-beautify";
import it from "./jsdom.js";
import * as snapshots from "./snapshots/index.js";

for (const [name, snapshot] of Object.entries(snapshots)) {
  it(`snapshot ${name}`, async () => {
    const svg = await snapshot();
    svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "http://www.w3.org/2000/svg");
    svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
    const actual = beautify.html(svg.outerHTML, {indent_size: 2});
    const outfile = path.resolve("./test/output", `${path.basename(name, ".js")}.svg`);
    const diffile = path.resolve("./test/output", `${path.basename(name, ".js")}-changed.svg`);
    let expected;

    try {
      expected = await fs.readFile(outfile, "utf8");
    } catch (error) {
      if (error.code === "ENOENT" && process.env.CI !== "true") {
        console.warn(`! generating ${outfile}`);
        await fs.writeFile(outfile, actual, "utf8");
        return;
      } else {
        throw error;
      }
    }

    if (actual === expected) {
      if (process.env.CI !== "true") {
        try {
          await fs.unlink(diffile);
          console.warn(`! deleted ${diffile}`);
        } catch (error) {
          if (error.code !== "ENOENT") {
            throw error;
          }
        }
      }
    } else {
      console.warn(`! generating ${diffile}`);
      await fs.writeFile(diffile, actual, "utf8");
    }

    assert(actual === expected, `${name} must match snapshot`);
  });
}


================================================
FILE: test/snapshots/index.js
================================================
import {extent, ticks} from "d3-array";
import {axisBottom, axisLeft} from "d3-axis";
import {autoType} from "d3-dsv";
import {tsv} from "d3-fetch";
import {geoPath} from "d3-geo";
import {scaleLinear} from "d3-scale";
import {select} from "d3-selection";
import {svg} from "htl";
import {contourDensity} from "d3-contour";

export async function faithfulContours() {
  const faithful = await tsv("data/faithful.tsv", autoType);

  const width = 960,
        height = 500,
        marginTop = 20,
        marginRight = 30,
        marginBottom = 30,
        marginLeft = 40;

  const x = scaleLinear()
      .domain(extent(faithful, d => d.waiting)).nice()
      .rangeRound([marginLeft, width - marginRight]);

  const y = scaleLinear()
      .domain(extent(faithful, d => d.eruptions)).nice()
      .rangeRound([height - marginBottom, marginTop]);

  const contours = contourDensity()
      .x(d => x(d.waiting))
      .y(d => y(d.eruptions))
      .size([width, height])
      .bandwidth(30)
      .thresholds(30)
    (faithful);

  const path = geoPath();

  return svg`<svg viewBox="0 0 ${width} ${height}" width=${width} height=${height} style="max-width: 100%; height: auto; height: intrinsic;">
    ${select(svg`<g transform="translate(${marginLeft},0)">`).call(axisLeft(y)).call(g => g.select(".domain").remove())}
    ${select(svg`<g transform="translate(0,${height - marginBottom})">`).call(axisBottom(x)).call(g => g.select(".domain").remove())}
    <g>${faithful.map(d => svg`<circle cx=${x(d.waiting)} cy=${y(d.eruptions)} r=2>`)}</g>
    <g fill=none stroke=blue>${contours.map(c => svg`<path d=${path(c)}>`)}</g>
  </svg>`;
}

export async function faithfulContour() {
  const faithful = await tsv("data/faithful.tsv", autoType);

  const width = 960,
        height = 500,
        marginTop = 20,
        marginRight = 30,
        marginBottom = 30,
        marginLeft = 40;

  const x = scaleLinear()
      .domain(extent(faithful, d => d.waiting)).nice()
      .rangeRound([marginLeft, width - marginRight]);

  const y = scaleLinear()
      .domain(extent(faithful, d => d.eruptions)).nice()
      .rangeRound([height - marginBottom, marginTop]);

  const contour = contourDensity()
      .x(d => x(d.waiting))
      .y(d => y(d.eruptions))
      .size([width, height])
      .bandwidth(30)
    .contours(faithful);

  const thresholds = ticks(0, contour.max, 30).slice(1);

  const path = geoPath();

  return svg`<svg viewBox="0 0 ${width} ${height}" width=${width} height=${height} style="max-width: 100%; height: auto; height: intrinsic;">
    ${select(svg`<g transform="translate(${marginLeft},0)">`).call(axisLeft(y)).call(g => g.select(".domain").remove())}
    ${select(svg`<g transform="translate(0,${height - marginBottom})">`).call(axisBottom(x)).call(g => g.select(".domain").remove())}
    <g>${faithful.map(d => svg`<circle cx=${x(d.waiting)} cy=${y(d.eruptions)} r=2>`)}</g>
    <g fill=none stroke=red>${thresholds.map(t => svg`<path d=${path(contour(t))}>`)}</g>
  </svg>`;
}
Download .txt
gitextract_9aal4xuq/

├── .eslintrc.json
├── .github/
│   ├── eslint.json
│   └── workflows/
│       └── node.js.yml
├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src/
│   ├── area.js
│   ├── array.js
│   ├── ascending.js
│   ├── constant.js
│   ├── contains.js
│   ├── contours.js
│   ├── density.js
│   ├── index.js
│   └── noop.js
└── test/
    ├── .eslintrc.json
    ├── asserts.js
    ├── contours-test.js
    ├── data/
    │   └── faithful.tsv
    ├── density-test.js
    ├── jsdom.js
    ├── snapshot.js
    └── snapshots/
        └── index.js
Download .txt
SYMBOL INDEX (35 symbols across 6 files)

FILE: src/contains.js
  function ringContains (line 7) | function ringContains(ring, point) {
  function segmentContains (line 17) | function segmentContains(a, b, c) {
  function collinear (line 21) | function collinear(a, b, c) {
  function within (line 25) | function within(p, q, r) {

FILE: src/contours.js
  function contours (line 34) | function contours(values) {
  function contour (line 52) | function contour(values, value) {
  function isorings (line 83) | function isorings(values, value, callback) {
  function index (line 164) | function index(point) {
  function smoothLinear (line 168) | function smoothLinear(ring, values, value) {
  function finite (line 205) | function finite(x) {
  function above (line 211) | function above(x, value) {
  function valid (line 216) | function valid(v) {
  function smooth1 (line 220) | function smooth1(x, v0, v1, value) {

FILE: src/density.js
  function defaultX (line 6) | function defaultX(d) {
  function defaultY (line 10) | function defaultY(d) {
  function defaultWeight (line 14) | function defaultWeight() {
  function grid (line 31) | function grid(data) {
  function density (line 56) | function density(data) {
  function transform (line 87) | function transform(geometry) {
  function transformPolygon (line 92) | function transformPolygon(coordinates) {
  function transformRing (line 96) | function transformRing(coordinates) {
  function transformPoint (line 101) | function transformPoint(coordinates) {
  function resize (line 106) | function resize() {

FILE: test/asserts.js
  function assertInDelta (line 3) | function assertInDelta(actual, expected, delta = 1e-6) {
  function inDelta (line 7) | function inDelta(actual, expected, delta) {
  function inDeltaArray (line 11) | function inDeltaArray(actual, expected, delta) {
  function inDeltaNumber (line 18) | function inDeltaNumber(actual, expected, delta) {

FILE: test/jsdom.js
  function jsdomit (line 5) | function jsdomit(description, run) {
  function withJsdom (line 17) | function withJsdom(run) {
  class Response (line 43) | class Response {
    method constructor (line 44) | constructor(href) {
    method text (line 49) | async text() {
    method json (line 52) | async json() {

FILE: test/snapshots/index.js
  function faithfulContours (line 11) | async function faithfulContours() {
  function faithfulContour (line 47) | async function faithfulContour() {
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (46K chars).
[
  {
    "path": ".eslintrc.json",
    "chars": 389,
    "preview": "{\n  \"extends\": \"eslint:recommended\",\n  \"parserOptions\": {\n    \"sourceType\": \"module\",\n    \"ecmaVersion\": 2020\n  },\n  \"en"
  },
  {
    "path": ".github/eslint.json",
    "chars": 367,
    "preview": "{\n  \"problemMatcher\": [\n    {\n      \"owner\": \"eslint-compact\",\n      \"pattern\": [\n        {\n          \"regexp\": \"^(.+):\\"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "chars": 648,
    "preview": "# https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions\n\nname: Node.js CI\n\non:\n"
  },
  {
    "path": ".gitignore",
    "chars": 63,
    "preview": "*.sublime-workspace\n.DS_Store\ndist/\nnode_modules\nnpm-debug.log\n"
  },
  {
    "path": "LICENSE",
    "chars": 731,
    "preview": "Copyright 2012-2023 Mike Bostock\n\nPermission to use, copy, modify, and/or distribute this software for any purpose\nwith "
  },
  {
    "path": "README.md",
    "chars": 525,
    "preview": "# d3-contour\n\n<a href=\"https://d3js.org\"><img src=\"https://github.com/d3/d3/raw/main/docs/public/logo.svg\" width=\"256\" h"
  },
  {
    "path": "package.json",
    "chars": 1971,
    "preview": "{\n  \"name\": \"d3-contour\",\n  \"version\": \"4.0.2\",\n  \"description\": \"Compute contour polygons using marching squares.\",\n  \""
  },
  {
    "path": "rollup.config.js",
    "chars": 1105,
    "preview": "import {readFileSync} from \"fs\";\nimport {terser} from \"rollup-plugin-terser\";\nimport meta from \"./package.json\" assert {"
  },
  {
    "path": "src/area.js",
    "chars": 230,
    "preview": "export default function(ring) {\n  var i = 0, n = ring.length, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring"
  },
  {
    "path": "src/array.js",
    "chars": 62,
    "preview": "var array = Array.prototype;\n\nexport var slice = array.slice;\n"
  },
  {
    "path": "src/ascending.js",
    "chars": 50,
    "preview": "export default function(a, b) {\n  return a - b;\n}\n"
  },
  {
    "path": "src/constant.js",
    "chars": 29,
    "preview": "export default x => () => x;\n"
  },
  {
    "path": "src/contains.js",
    "chars": 871,
    "preview": "export default function(ring, hole) {\n  var i = -1, n = hole.length, c;\n  while (++i < n) if (c = ringContains(ring, hol"
  },
  {
    "path": "src/contours.js",
    "chars": 6973,
    "preview": "import {extent, nice, thresholdSturges, ticks} from \"d3-array\";\nimport {slice} from \"./array.js\";\nimport ascending from "
  },
  {
    "path": "src/density.js",
    "chars": 4185,
    "preview": "import {blur2, max, ticks} from \"d3-array\";\nimport {slice} from \"./array.js\";\nimport constant from \"./constant.js\";\nimpo"
  },
  {
    "path": "src/index.js",
    "chars": 107,
    "preview": "export {default as contours} from \"./contours.js\";\nexport {default as contourDensity} from \"./density.js\";\n"
  },
  {
    "path": "src/noop.js",
    "chars": 29,
    "preview": "export default function() {}\n"
  },
  {
    "path": "test/.eslintrc.json",
    "chars": 37,
    "preview": "{\n  \"env\": {\n    \"mocha\": true\n  }\n}\n"
  },
  {
    "path": "test/asserts.js",
    "chars": 625,
    "preview": "import assert from \"assert\";\n\nexport function assertInDelta(actual, expected, delta = 1e-6) {\n  assert(inDelta(actual, e"
  },
  {
    "path": "test/contours-test.js",
    "chars": 11296,
    "preview": "import assert from \"assert\";\nimport {contours} from \"../src/index.js\";\n\nit(\"contours(values) returns the expected result"
  },
  {
    "path": "test/data/faithful.tsv",
    "chars": 2466,
    "preview": "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.9"
  },
  {
    "path": "test/density-test.js",
    "chars": 3909,
    "preview": "import assert from \"assert\";\nimport {extent, ticks} from \"d3-array\";\nimport {autoType} from \"d3-dsv\";\nimport {tsv} from "
  },
  {
    "path": "test/jsdom.js",
    "chars": 1390,
    "preview": "import {promises as fs} from \"fs\";\nimport * as path from \"path\";\nimport {JSDOM} from \"jsdom\";\n\nexport default function j"
  },
  {
    "path": "test/snapshot.js",
    "chars": 1614,
    "preview": "import assert from \"assert\";\nimport {promises as fs} from \"fs\";\nimport * as path from \"path\";\nimport beautify from \"js-b"
  },
  {
    "path": "test/snapshots/index.js",
    "chars": 3013,
    "preview": "import {extent, ticks} from \"d3-array\";\nimport {axisBottom, axisLeft} from \"d3-axis\";\nimport {autoType} from \"d3-dsv\";\ni"
  }
]

About this extraction

This page contains the full source code of the d3/d3-contour GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (41.7 KB), approximately 17.8k tokens, and a symbol index with 35 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!