diff options
author | Matt Kohls <mattkohls13@gmail.com> | 2023-12-04 22:44:20 -0500 |
---|---|---|
committer | Matt Kohls <mattkohls13@gmail.com> | 2023-12-04 23:55:28 -0500 |
commit | 8cf24308185d45000b1ce6f29a2edc6cdf00185c (patch) | |
tree | 27cce5a5f7cde07cd8b100e3a9b840a5997e00ac | |
parent | 9bd50e8bc54dedae84b3eb424d5eba54bfa870f3 (diff) | |
download | sensor-aggregator-8cf24308185d45000b1ce6f29a2edc6cdf00185c.tar.gz sensor-aggregator-8cf24308185d45000b1ce6f29a2edc6cdf00185c.tar.bz2 sensor-aggregator-8cf24308185d45000b1ce6f29a2edc6cdf00185c.zip |
Using new api + D3 and Plot to draw fancy graphs
-rw-r--r-- | snag/dashboard.py | 4 | ||||
-rw-r--r-- | snag/data.py | 148 | ||||
-rw-r--r-- | snag/static/d3.js | 20624 | ||||
-rw-r--r-- | snag/static/pagedown.css | 7 | ||||
-rw-r--r-- | snag/static/plot.js | 14264 | ||||
-rw-r--r-- | snag/templates/dashboard/dashboard.html | 133 |
6 files changed, 35105 insertions, 75 deletions
diff --git a/snag/dashboard.py b/snag/dashboard.py index 7e77124..1eca82c 100644 --- a/snag/dashboard.py +++ b/snag/dashboard.py @@ -16,11 +16,11 @@ def dashboard(): db = get_db() rows = db.execute( - 'SELECT de.deviceId, de.environment, d.deviceName' + 'SELECT de.deviceId, de.environment, de.environmentDescription, d.deviceName, d.deviceLocation' ' FROM device_env de JOIN devices d ON de.deviceId = d.deviceId' ' ORDER BY entry DESC' ).fetchall() - deviceList = [dict(deviceId=row['deviceId'], location=row['environment'], name=row['deviceName']) for row in rows] + deviceList = [dict(deviceId=row['deviceId'], environment=row['environment'], environmentDesc=row['environmentDescription'], name=row['deviceName'], location=row['deviceLocation']) for row in rows] return render_template('dashboard/dashboard.html', deviceList=deviceList, generatedAt=datetime.now()) diff --git a/snag/data.py b/snag/data.py index 7e65186..637f7f0 100644 --- a/snag/data.py +++ b/snag/data.py @@ -8,6 +8,7 @@ from flask import ( from werkzeug.exceptions import abort from datetime import datetime, timedelta from snag.db import get_db +from snag.graphs import get_table import time bp = Blueprint('data', __name__, url_prefix='/data') @@ -16,7 +17,7 @@ bp = Blueprint('data', __name__, url_prefix='/data') def is_dst_aware(deviceId): db = get_db() sql = "SELECT dstAware FROM devices WHERE deviceId = ?" - data = db.execute(sql, deviceId) + data = db.execute(sql, [deviceId]) if data is None: return False else: @@ -95,6 +96,7 @@ def unpack_json_scheme_1(req): return "Data received", 200 +## For submitting data to server @bp.route('/json', methods=['POST']) def add_json_data(): if request.is_json: @@ -110,8 +112,11 @@ def add_json_data(): else: abort(400, "Unknown payload type") -@bp.route('/devices', methods=['GET']) -def get_devices(): +### Information Routes ### + +## Gives information about all the devices +@bp.route('/info/devices', methods=['GET', 'POST']) +def get_info_devices(): db = get_db() out = [] @@ -134,22 +139,9 @@ def get_devices(): out.append(deviceLine) return out -def parse_measuremet_req(req): - req = req.lower() - if req == "": - req = "thp" - measurements = "date" - if 't' in req: - measurements = measurements + ", temperature" - if 'h' in req: - measurements = measurements + ", humidity" - if 'p' in req: - measurements = measurements + ", pressure" - - return measurements ## Grabs data from db, or None if not -def get_data_selection(device_id, start_time, end_time, columns, environment): +def get_data_selection(device_ids, start_time, end_time, columns, environment): if end_time is None: end_time = datetime.now() if start_time is None: @@ -159,15 +151,43 @@ def get_data_selection(device_id, start_time, end_time, columns, environment): db = get_db() if environment is not None: + devqs = "{0}".format(', '.join(['?'] * len(device_ids))) + argument_list = [start_time.isoformat(" ", "seconds"), end_time.isoformat(" ", "seconds")] + device_ids sql = (f"SELECT {columns} " f"FROM {environment} " - "WHERE (date BETWEEN ? AND ?) AND deviceId = ?") - subset = db.execute(sql, [start_time.isoformat(" ", "seconds"), end_time.isoformat(" ", "seconds"), device_id]).fetchall() + f"WHERE (date BETWEEN ? AND ?) AND deviceId IN ( {devqs} )") + subset = db.execute(sql, argument_list).fetchall() else: subset = None return subset +### Device Routes ### + +## Get data by devices +# +# Expected json payload +# { +# "devices" : [ "", "", .. ] ; Required +# "environment" : "", ; Required +# "startTime" : "", ; Optional +# "endTime" : "", ; Optional +# "measurements" : [ "", "", .. ] ; Required +# } +# +# Returns a json payload like this +# [ +# {"timeStamp":"2112-09-21T09:21:00", "":"", ..}, .. +# ] +# +@bp.route('/device', methods=['GET', 'POST']) +def get_n_device_data(): + if request.is_json: + payload = request.get_json() + return process_device_data_req(payload) + else: + abort(400, "Missing request data") + ## Get data by deviceId # # Expected json payload @@ -183,44 +203,72 @@ def get_data_selection(device_id, start_time, end_time, columns, environment): # {"timeStamp":"2112-09-21T09:21:00", "":"", ..}, .. # ] # -@bp.route('/<int:device_id>', methods=['GET']) +@bp.route('/device/<int:device_id>', methods=['GET', 'POST']) def get_device_data(device_id): if request.is_json: payload = request.get_json() - if 'environment' in payload and 'measurements' in payload: - env = payload['environment'] - end_time = datetime.now() - start_time = end_time - timedelta(hours=1) - if 'startTime' in payload: - start_time = payload['startTime'] - start_time = datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%SZ") - if 'endTime' in payload: - end_time = payload['endTime'] - end_time = datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%SZ") - selected_columns = "date" - for measurement in payload['measurements']: - selected_columns = selected_columns + ", " + measurement - if selected_columns == "date": - abort(400, "Bad json payload - missing measurements") - else: - data = get_data_selection(device_id, start_time, end_time, selected_columns, env) - if data is None: - abort(404, "No data found") - - outlist = [] - for row in data: - reading = { "timeStamp":row["date"].strftime('%Y-%m-%dT%H:%M:%SZ') } - for measurement in payload['measurements']: - reading[measurement] = row[measurement] - outlist.append(reading) - return outlist - else: - abort(400, "Bad json payload") + payload['devices'] = [device_id] + return process_device_data_req(payload) else: abort(400, "Missing request data") +## Processes a device data request +# +# Expected json payload +# { +# "devices" : [ "", "", .. ] ; Required +# "environment" : "", ; Required +# "startTime" : "", ; Optional +# "endTime" : "", ; Optional +# "measurements" : [ "", "", .. ] ; Required +# } +# +# Returns a json payload like this +# [ +# {"timeStamp":"2112-09-21T09:21:00", "":"", ..}, .. +# ] +# +def process_device_data_req(payload): + if 'environment' in payload and 'measurements' in payload and 'devices' in payload: + env = get_table(payload['environment']) + if env is None: + abort(400, "Unknown environment: " + payload['environment']) + end_time = datetime.now() + start_time = end_time - timedelta(days=1) + if 'startTime' in payload: + start_time = payload['startTime'] + start_time = datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%SZ") + if 'endTime' in payload: + end_time = payload['endTime'] + end_time = datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%SZ") + selected_columns = "date, deviceId" + for measurement in payload['measurements']: + selected_columns = selected_columns + ", " + measurement + if selected_columns == "date, deviceId": + abort(400, "Bad json payload - missing measurements") + else: + device_ids = [] + for device in payload['devices']: + device_ids.append(device) + if device_ids == []: + abort(400, "Bad json payload - missing devices") + data = get_data_selection(device_ids, start_time, end_time, selected_columns, env) + if data is None: + abort(404, "No data found") + + outlist = [] + for row in data: + reading = { "timeStamp":row["date"].strftime('%Y-%m-%dT%H:%M:%SZ'), + "deviceId":row["deviceId"]} + for measurement in payload['measurements']: + reading[measurement] = row[measurement] + outlist.append(reading) + return outlist + else: + abort(400, "Bad json payload") + # Get data by environment type -@bp.route('/environment', methods=['GET']) +@bp.route('/environment', methods=['GET', 'POST']) def get_env_data(): if request.args['type'] is not None: return "todo" diff --git a/snag/static/d3.js b/snag/static/d3.js new file mode 100644 index 0000000..c1f71bc --- /dev/null +++ b/snag/static/d3.js @@ -0,0 +1,20624 @@ +// https://d3js.org v7.8.5 Copyright 2010-2023 Mike Bostock +(function (global, factory) { +typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : +typeof define === 'function' && define.amd ? define(['exports'], factory) : +(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3 = global.d3 || {})); +})(this, (function (exports) { 'use strict'; + +var version = "7.8.5"; + +function ascending$3(a, b) { + return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; +} + +function descending$2(a, b) { + return a == null || b == null ? NaN + : b < a ? -1 + : b > a ? 1 + : b >= a ? 0 + : NaN; +} + +function bisector(f) { + let compare1, compare2, delta; + + // If an accessor is specified, promote it to a comparator. In this case we + // can test whether the search value is (self-) comparable. We can’t do this + // for a comparator (except for specific, known comparators) because we can’t + // tell if the comparator is symmetric, and an asymmetric comparator can’t be + // used to test whether a single value is comparable. + if (f.length !== 2) { + compare1 = ascending$3; + compare2 = (d, x) => ascending$3(f(d), x); + delta = (d, x) => f(d) - x; + } else { + compare1 = f === ascending$3 || f === descending$2 ? f : zero$1; + compare2 = f; + delta = f; + } + + function left(a, x, lo = 0, hi = a.length) { + if (lo < hi) { + if (compare1(x, x) !== 0) return hi; + do { + const mid = (lo + hi) >>> 1; + if (compare2(a[mid], x) < 0) lo = mid + 1; + else hi = mid; + } while (lo < hi); + } + return lo; + } + + function right(a, x, lo = 0, hi = a.length) { + if (lo < hi) { + if (compare1(x, x) !== 0) return hi; + do { + const mid = (lo + hi) >>> 1; + if (compare2(a[mid], x) <= 0) lo = mid + 1; + else hi = mid; + } while (lo < hi); + } + return lo; + } + + function center(a, x, lo = 0, hi = a.length) { + const i = left(a, x, lo, hi - 1); + return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i; + } + + return {left, center, right}; +} + +function zero$1() { + return 0; +} + +function number$3(x) { + return x === null ? NaN : +x; +} + +function* numbers(values, valueof) { + if (valueof === undefined) { + for (let value of values) { + if (value != null && (value = +value) >= value) { + yield value; + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { + yield value; + } + } + } +} + +const ascendingBisect = bisector(ascending$3); +const bisectRight = ascendingBisect.right; +const bisectLeft = ascendingBisect.left; +const bisectCenter = bisector(number$3).center; +var bisect = bisectRight; + +function blur(values, r) { + if (!((r = +r) >= 0)) throw new RangeError("invalid r"); + let length = values.length; + if (!((length = Math.floor(length)) >= 0)) throw new RangeError("invalid length"); + if (!length || !r) return values; + const blur = blurf(r); + const temp = values.slice(); + blur(values, temp, 0, length, 1); + blur(temp, values, 0, length, 1); + blur(values, temp, 0, length, 1); + return values; +} + +const blur2 = Blur2(blurf); + +const blurImage = Blur2(blurfImage); + +function Blur2(blur) { + return function(data, rx, ry = rx) { + if (!((rx = +rx) >= 0)) throw new RangeError("invalid rx"); + if (!((ry = +ry) >= 0)) throw new RangeError("invalid ry"); + let {data: values, width, height} = data; + if (!((width = Math.floor(width)) >= 0)) throw new RangeError("invalid width"); + if (!((height = Math.floor(height !== undefined ? height : values.length / width)) >= 0)) throw new RangeError("invalid height"); + if (!width || !height || (!rx && !ry)) return data; + const blurx = rx && blur(rx); + const blury = ry && blur(ry); + const temp = values.slice(); + if (blurx && blury) { + blurh(blurx, temp, values, width, height); + blurh(blurx, values, temp, width, height); + blurh(blurx, temp, values, width, height); + blurv(blury, values, temp, width, height); + blurv(blury, temp, values, width, height); + blurv(blury, values, temp, width, height); + } else if (blurx) { + blurh(blurx, values, temp, width, height); + blurh(blurx, temp, values, width, height); + blurh(blurx, values, temp, width, height); + } else if (blury) { + blurv(blury, values, temp, width, height); + blurv(blury, temp, values, width, height); + blurv(blury, values, temp, width, height); + } + return data; + }; +} + +function blurh(blur, T, S, w, h) { + for (let y = 0, n = w * h; y < n;) { + blur(T, S, y, y += w, 1); + } +} + +function blurv(blur, T, S, w, h) { + for (let x = 0, n = w * h; x < w; ++x) { + blur(T, S, x, x + n, w); + } +} + +function blurfImage(radius) { + const blur = blurf(radius); + return (T, S, start, stop, step) => { + start <<= 2, stop <<= 2, step <<= 2; + blur(T, S, start + 0, stop + 0, step); + blur(T, S, start + 1, stop + 1, step); + blur(T, S, start + 2, stop + 2, step); + blur(T, S, start + 3, stop + 3, step); + }; +} + +// Given a target array T, a source array S, sets each value T[i] to the average +// of {S[i - r], …, S[i], …, S[i + r]}, where r = ⌊radius⌋, start <= i < stop, +// for each i, i + step, i + 2 * step, etc., and where S[j] is clamped between +// S[start] (inclusive) and S[stop] (exclusive). If the given radius is not an +// integer, S[i - r - 1] and S[i + r + 1] are added to the sum, each weighted +// according to r - ⌊radius⌋. +function blurf(radius) { + const radius0 = Math.floor(radius); + if (radius0 === radius) return bluri(radius); + const t = radius - radius0; + const w = 2 * radius + 1; + return (T, S, start, stop, step) => { // stop must be aligned! + if (!((stop -= step) >= start)) return; // inclusive stop + let sum = radius0 * S[start]; + const s0 = step * radius0; + const s1 = s0 + step; + for (let i = start, j = start + s0; i < j; i += step) { + sum += S[Math.min(stop, i)]; + } + for (let i = start, j = stop; i <= j; i += step) { + sum += S[Math.min(stop, i + s0)]; + T[i] = (sum + t * (S[Math.max(start, i - s1)] + S[Math.min(stop, i + s1)])) / w; + sum -= S[Math.max(start, i - s0)]; + } + }; +} + +// Like blurf, but optimized for integer radius. +function bluri(radius) { + const w = 2 * radius + 1; + return (T, S, start, stop, step) => { // stop must be aligned! + if (!((stop -= step) >= start)) return; // inclusive stop + let sum = radius * S[start]; + const s = step * radius; + for (let i = start, j = start + s; i < j; i += step) { + sum += S[Math.min(stop, i)]; + } + for (let i = start, j = stop; i <= j; i += step) { + sum += S[Math.min(stop, i + s)]; + T[i] = sum / w; + sum -= S[Math.max(start, i - s)]; + } + }; +} + +function count$1(values, valueof) { + let count = 0; + if (valueof === undefined) { + for (let value of values) { + if (value != null && (value = +value) >= value) { + ++count; + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { + ++count; + } + } + } + return count; +} + +function length$3(array) { + return array.length | 0; +} + +function empty$2(length) { + return !(length > 0); +} + +function arrayify(values) { + return typeof values !== "object" || "length" in values ? values : Array.from(values); +} + +function reducer(reduce) { + return values => reduce(...values); +} + +function cross$2(...values) { + const reduce = typeof values[values.length - 1] === "function" && reducer(values.pop()); + values = values.map(arrayify); + const lengths = values.map(length$3); + const j = values.length - 1; + const index = new Array(j + 1).fill(0); + const product = []; + if (j < 0 || lengths.some(empty$2)) return product; + while (true) { + product.push(index.map((j, i) => values[i][j])); + let i = j; + while (++index[i] === lengths[i]) { + if (i === 0) return reduce ? product.map(reduce) : product; + index[i--] = 0; + } + } +} + +function cumsum(values, valueof) { + var sum = 0, index = 0; + return Float64Array.from(values, valueof === undefined + ? v => (sum += +v || 0) + : v => (sum += +valueof(v, index++, values) || 0)); +} + +function variance(values, valueof) { + let count = 0; + let delta; + let mean = 0; + let sum = 0; + if (valueof === undefined) { + for (let value of values) { + if (value != null && (value = +value) >= value) { + delta = value - mean; + mean += delta / ++count; + sum += delta * (value - mean); + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { + delta = value - mean; + mean += delta / ++count; + sum += delta * (value - mean); + } + } + } + if (count > 1) return sum / (count - 1); +} + +function deviation(values, valueof) { + const v = variance(values, valueof); + return v ? Math.sqrt(v) : v; +} + +function extent$1(values, valueof) { + let min; + let max; + if (valueof === undefined) { + for (const value of values) { + if (value != null) { + if (min === undefined) { + if (value >= value) min = max = value; + } else { + if (min > value) min = value; + if (max < value) max = value; + } + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null) { + if (min === undefined) { + if (value >= value) min = max = value; + } else { + if (min > value) min = value; + if (max < value) max = value; + } + } + } + } + return [min, max]; +} + +// https://github.com/python/cpython/blob/a74eea238f5baba15797e2e8b570d153bc8690a7/Modules/mathmodule.c#L1423 +class Adder { + constructor() { + this._partials = new Float64Array(32); + this._n = 0; + } + add(x) { + const p = this._partials; + let i = 0; + for (let j = 0; j < this._n && j < 32; j++) { + const y = p[j], + hi = x + y, + lo = Math.abs(x) < Math.abs(y) ? x - (hi - y) : y - (hi - x); + if (lo) p[i++] = lo; + x = hi; + } + p[i] = x; + this._n = i + 1; + return this; + } + valueOf() { + const p = this._partials; + let n = this._n, x, y, lo, hi = 0; + if (n > 0) { + hi = p[--n]; + while (n > 0) { + x = hi; + y = p[--n]; + hi = x + y; + lo = y - (hi - x); + if (lo) break; + } + if (n > 0 && ((lo < 0 && p[n - 1] < 0) || (lo > 0 && p[n - 1] > 0))) { + y = lo * 2; + x = hi + y; + if (y == x - hi) hi = x; + } + } + return hi; + } +} + +function fsum(values, valueof) { + const adder = new Adder(); + if (valueof === undefined) { + for (let value of values) { + if (value = +value) { + adder.add(value); + } + } + } else { + let index = -1; + for (let value of values) { + if (value = +valueof(value, ++index, values)) { + adder.add(value); + } + } + } + return +adder; +} + +function fcumsum(values, valueof) { + const adder = new Adder(); + let index = -1; + return Float64Array.from(values, valueof === undefined + ? v => adder.add(+v || 0) + : v => adder.add(+valueof(v, ++index, values) || 0) + ); +} + +class InternMap extends Map { + constructor(entries, key = keyof) { + super(); + Object.defineProperties(this, {_intern: {value: new Map()}, _key: {value: key}}); + if (entries != null) for (const [key, value] of entries) this.set(key, value); + } + get(key) { + return super.get(intern_get(this, key)); + } + has(key) { + return super.has(intern_get(this, key)); + } + set(key, value) { + return super.set(intern_set(this, key), value); + } + delete(key) { + return super.delete(intern_delete(this, key)); + } +} + +class InternSet extends Set { + constructor(values, key = keyof) { + super(); + Object.defineProperties(this, {_intern: {value: new Map()}, _key: {value: key}}); + if (values != null) for (const value of values) this.add(value); + } + has(value) { + return super.has(intern_get(this, value)); + } + add(value) { + return super.add(intern_set(this, value)); + } + delete(value) { + return super.delete(intern_delete(this, value)); + } +} + +function intern_get({_intern, _key}, value) { + const key = _key(value); + return _intern.has(key) ? _intern.get(key) : value; +} + +function intern_set({_intern, _key}, value) { + const key = _key(value); + if (_intern.has(key)) return _intern.get(key); + _intern.set(key, value); + return value; +} + +function intern_delete({_intern, _key}, value) { + const key = _key(value); + if (_intern.has(key)) { + value = _intern.get(key); + _intern.delete(key); + } + return value; +} + +function keyof(value) { + return value !== null && typeof value === "object" ? value.valueOf() : value; +} + +function identity$9(x) { + return x; +} + +function group(values, ...keys) { + return nest(values, identity$9, identity$9, keys); +} + +function groups(values, ...keys) { + return nest(values, Array.from, identity$9, keys); +} + +function flatten$1(groups, keys) { + for (let i = 1, n = keys.length; i < n; ++i) { + groups = groups.flatMap(g => g.pop().map(([key, value]) => [...g, key, value])); + } + return groups; +} + +function flatGroup(values, ...keys) { + return flatten$1(groups(values, ...keys), keys); +} + +function flatRollup(values, reduce, ...keys) { + return flatten$1(rollups(values, reduce, ...keys), keys); +} + +function rollup(values, reduce, ...keys) { + return nest(values, identity$9, reduce, keys); +} + +function rollups(values, reduce, ...keys) { + return nest(values, Array.from, reduce, keys); +} + +function index$4(values, ...keys) { + return nest(values, identity$9, unique, keys); +} + +function indexes(values, ...keys) { + return nest(values, Array.from, unique, keys); +} + +function unique(values) { + if (values.length !== 1) throw new Error("duplicate key"); + return values[0]; +} + +function nest(values, map, reduce, keys) { + return (function regroup(values, i) { + if (i >= keys.length) return reduce(values); + const groups = new InternMap(); + const keyof = keys[i++]; + let index = -1; + for (const value of values) { + const key = keyof(value, ++index, values); + const group = groups.get(key); + if (group) group.push(value); + else groups.set(key, [value]); + } + for (const [key, values] of groups) { + groups.set(key, regroup(values, i)); + } + return map(groups); + })(values, 0); +} + +function permute(source, keys) { + return Array.from(keys, key => source[key]); +} + +function sort(values, ...F) { + if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); + values = Array.from(values); + let [f] = F; + if ((f && f.length !== 2) || F.length > 1) { + const index = Uint32Array.from(values, (d, i) => i); + if (F.length > 1) { + F = F.map(f => values.map(f)); + index.sort((i, j) => { + for (const f of F) { + const c = ascendingDefined(f[i], f[j]); + if (c) return c; + } + }); + } else { + f = values.map(f); + index.sort((i, j) => ascendingDefined(f[i], f[j])); + } + return permute(values, index); + } + return values.sort(compareDefined(f)); +} + +function compareDefined(compare = ascending$3) { + if (compare === ascending$3) return ascendingDefined; + if (typeof compare !== "function") throw new TypeError("compare is not a function"); + return (a, b) => { + const x = compare(a, b); + if (x || x === 0) return x; + return (compare(b, b) === 0) - (compare(a, a) === 0); + }; +} + +function ascendingDefined(a, b) { + return (a == null || !(a >= a)) - (b == null || !(b >= b)) || (a < b ? -1 : a > b ? 1 : 0); +} + +function groupSort(values, reduce, key) { + return (reduce.length !== 2 + ? sort(rollup(values, reduce, key), (([ak, av], [bk, bv]) => ascending$3(av, bv) || ascending$3(ak, bk))) + : sort(group(values, key), (([ak, av], [bk, bv]) => reduce(av, bv) || ascending$3(ak, bk)))) + .map(([key]) => key); +} + +var array$5 = Array.prototype; + +var slice$3 = array$5.slice; + +function constant$b(x) { + return () => x; +} + +const e10 = Math.sqrt(50), + e5 = Math.sqrt(10), + e2 = Math.sqrt(2); + +function tickSpec(start, stop, count) { + const step = (stop - start) / Math.max(0, count), + power = Math.floor(Math.log10(step)), + error = step / Math.pow(10, power), + factor = error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1; + let i1, i2, inc; + if (power < 0) { + inc = Math.pow(10, -power) / factor; + i1 = Math.round(start * inc); + i2 = Math.round(stop * inc); + if (i1 / inc < start) ++i1; + if (i2 / inc > stop) --i2; + inc = -inc; + } else { + inc = Math.pow(10, power) * factor; + i1 = Math.round(start / inc); + i2 = Math.round(stop / inc); + if (i1 * inc < start) ++i1; + if (i2 * inc > stop) --i2; + } + if (i2 < i1 && 0.5 <= count && count < 2) return tickSpec(start, stop, count * 2); + return [i1, i2, inc]; +} + +function ticks(start, stop, count) { + stop = +stop, start = +start, count = +count; + if (!(count > 0)) return []; + if (start === stop) return [start]; + const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count) : tickSpec(start, stop, count); + if (!(i2 >= i1)) return []; + const n = i2 - i1 + 1, ticks = new Array(n); + if (reverse) { + if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) / -inc; + else for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) * inc; + } else { + if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) / -inc; + else for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) * inc; + } + return ticks; +} + +function tickIncrement(start, stop, count) { + stop = +stop, start = +start, count = +count; + return tickSpec(start, stop, count)[2]; +} + +function tickStep(start, stop, count) { + stop = +stop, start = +start, count = +count; + const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count) : tickIncrement(start, stop, count); + return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc); +} + +function nice$1(start, stop, count) { + let prestep; + while (true) { + const step = tickIncrement(start, stop, count); + if (step === prestep || step === 0 || !isFinite(step)) { + return [start, stop]; + } else if (step > 0) { + start = Math.floor(start / step) * step; + stop = Math.ceil(stop / step) * step; + } else if (step < 0) { + start = Math.ceil(start * step) / step; + stop = Math.floor(stop * step) / step; + } + prestep = step; + } +} + +function thresholdSturges(values) { + return Math.max(1, Math.ceil(Math.log(count$1(values)) / Math.LN2) + 1); +} + +function bin() { + var value = identity$9, + domain = extent$1, + threshold = thresholdSturges; + + function histogram(data) { + if (!Array.isArray(data)) data = Array.from(data); + + var i, + n = data.length, + x, + step, + values = new Array(n); + + for (i = 0; i < n; ++i) { + values[i] = value(data[i], i, data); + } + + var xz = domain(values), + x0 = xz[0], + x1 = xz[1], + tz = threshold(values, x0, x1); + + // Convert number of thresholds into uniform thresholds, and nice the + // default domain accordingly. + if (!Array.isArray(tz)) { + const max = x1, tn = +tz; + if (domain === extent$1) [x0, x1] = nice$1(x0, x1, tn); + tz = ticks(x0, x1, tn); + + // If the domain is aligned with the first tick (which it will by + // default), then we can use quantization rather than bisection to bin + // values, which is substantially faster. + if (tz[0] <= x0) step = tickIncrement(x0, x1, tn); + + // If the last threshold is coincident with the domain’s upper bound, the + // last bin will be zero-width. If the default domain is used, and this + // last threshold is coincident with the maximum input value, we can + // extend the niced upper bound by one tick to ensure uniform bin widths; + // otherwise, we simply remove the last threshold. Note that we don’t + // coerce values or the domain to numbers, and thus must be careful to + // compare order (>=) rather than strict equality (===)! + if (tz[tz.length - 1] >= x1) { + if (max >= x1 && domain === extent$1) { + const step = tickIncrement(x0, x1, tn); + if (isFinite(step)) { + if (step > 0) { + x1 = (Math.floor(x1 / step) + 1) * step; + } else if (step < 0) { + x1 = (Math.ceil(x1 * -step) + 1) / -step; + } + } + } else { + tz.pop(); + } + } + } + + // Remove any thresholds outside the domain. + // Be careful not to mutate an array owned by the user! + var m = tz.length, a = 0, b = m; + while (tz[a] <= x0) ++a; + while (tz[b - 1] > x1) --b; + if (a || b < m) tz = tz.slice(a, b), m = b - a; + + var bins = new Array(m + 1), + bin; + + // Initialize bins. + for (i = 0; i <= m; ++i) { + bin = bins[i] = []; + bin.x0 = i > 0 ? tz[i - 1] : x0; + bin.x1 = i < m ? tz[i] : x1; + } + + // Assign data to bins by value, ignoring any outside the domain. + if (isFinite(step)) { + if (step > 0) { + for (i = 0; i < n; ++i) { + if ((x = values[i]) != null && x0 <= x && x <= x1) { + bins[Math.min(m, Math.floor((x - x0) / step))].push(data[i]); + } + } + } else if (step < 0) { + for (i = 0; i < n; ++i) { + if ((x = values[i]) != null && x0 <= x && x <= x1) { + const j = Math.floor((x0 - x) * step); + bins[Math.min(m, j + (tz[j] <= x))].push(data[i]); // handle off-by-one due to rounding + } + } + } + } else { + for (i = 0; i < n; ++i) { + if ((x = values[i]) != null && x0 <= x && x <= x1) { + bins[bisect(tz, x, 0, m)].push(data[i]); + } + } + } + + return bins; + } + + histogram.value = function(_) { + return arguments.length ? (value = typeof _ === "function" ? _ : constant$b(_), histogram) : value; + }; + + histogram.domain = function(_) { + return arguments.length ? (domain = typeof _ === "function" ? _ : constant$b([_[0], _[1]]), histogram) : domain; + }; + + histogram.thresholds = function(_) { + return arguments.length ? (threshold = typeof _ === "function" ? _ : constant$b(Array.isArray(_) ? slice$3.call(_) : _), histogram) : threshold; + }; + + return histogram; +} + +function max$3(values, valueof) { + let max; + if (valueof === undefined) { + for (const value of values) { + if (value != null + && (max < value || (max === undefined && value >= value))) { + max = value; + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null + && (max < value || (max === undefined && value >= value))) { + max = value; + } + } + } + return max; +} + +function maxIndex(values, valueof) { + let max; + let maxIndex = -1; + let index = -1; + if (valueof === undefined) { + for (const value of values) { + ++index; + if (value != null + && (max < value || (max === undefined && value >= value))) { + max = value, maxIndex = index; + } + } + } else { + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null + && (max < value || (max === undefined && value >= value))) { + max = value, maxIndex = index; + } + } + } + return maxIndex; +} + +function min$2(values, valueof) { + let min; + if (valueof === undefined) { + for (const value of values) { + if (value != null + && (min > value || (min === undefined && value >= value))) { + min = value; + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null + && (min > value || (min === undefined && value >= value))) { + min = value; + } + } + } + return min; +} + +function minIndex(values, valueof) { + let min; + let minIndex = -1; + let index = -1; + if (valueof === undefined) { + for (const value of values) { + ++index; + if (value != null + && (min > value || (min === undefined && value >= value))) { + min = value, minIndex = index; + } + } + } else { + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null + && (min > value || (min === undefined && value >= value))) { + min = value, minIndex = index; + } + } + } + return minIndex; +} + +// Based on https://github.com/mourner/quickselect +// ISC license, Copyright 2018 Vladimir Agafonkin. +function quickselect(array, k, left = 0, right = Infinity, compare) { + k = Math.floor(k); + left = Math.floor(Math.max(0, left)); + right = Math.floor(Math.min(array.length - 1, right)); + + if (!(left <= k && k <= right)) return array; + + compare = compare === undefined ? ascendingDefined : compareDefined(compare); + + while (right > left) { + if (right - left > 600) { + const n = right - left + 1; + const m = k - left + 1; + const z = Math.log(n); + const s = 0.5 * Math.exp(2 * z / 3); + const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + const newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + quickselect(array, k, newLeft, newRight, compare); + } + + const t = array[k]; + let i = left; + let j = right; + + swap$1(array, left, k); + if (compare(array[right], t) > 0) swap$1(array, left, right); + + while (i < j) { + swap$1(array, i, j), ++i, --j; + while (compare(array[i], t) < 0) ++i; + while (compare(array[j], t) > 0) --j; + } + + if (compare(array[left], t) === 0) swap$1(array, left, j); + else ++j, swap$1(array, j, right); + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } + + return array; +} + +function swap$1(array, i, j) { + const t = array[i]; + array[i] = array[j]; + array[j] = t; +} + +function greatest(values, compare = ascending$3) { + let max; + let defined = false; + if (compare.length === 1) { + let maxValue; + for (const element of values) { + const value = compare(element); + if (defined + ? ascending$3(value, maxValue) > 0 + : ascending$3(value, value) === 0) { + max = element; + maxValue = value; + defined = true; + } + } + } else { + for (const value of values) { + if (defined + ? compare(value, max) > 0 + : compare(value, value) === 0) { + max = value; + defined = true; + } + } + } + return max; +} + +function quantile$1(values, p, valueof) { + values = Float64Array.from(numbers(values, valueof)); + if (!(n = values.length) || isNaN(p = +p)) return; + if (p <= 0 || n < 2) return min$2(values); + if (p >= 1) return max$3(values); + var n, + i = (n - 1) * p, + i0 = Math.floor(i), + value0 = max$3(quickselect(values, i0).subarray(0, i0 + 1)), + value1 = min$2(values.subarray(i0 + 1)); + return value0 + (value1 - value0) * (i - i0); +} + +function quantileSorted(values, p, valueof = number$3) { + if (!(n = values.length) || isNaN(p = +p)) return; + if (p <= 0 || n < 2) return +valueof(values[0], 0, values); + if (p >= 1) return +valueof(values[n - 1], n - 1, values); + var n, + i = (n - 1) * p, + i0 = Math.floor(i), + value0 = +valueof(values[i0], i0, values), + value1 = +valueof(values[i0 + 1], i0 + 1, values); + return value0 + (value1 - value0) * (i - i0); +} + +function quantileIndex(values, p, valueof = number$3) { + if (isNaN(p = +p)) return; + numbers = Float64Array.from(values, (_, i) => number$3(valueof(values[i], i, values))); + if (p <= 0) return minIndex(numbers); + if (p >= 1) return maxIndex(numbers); + var numbers, + index = Uint32Array.from(values, (_, i) => i), + j = numbers.length - 1, + i = Math.floor(j * p); + quickselect(index, i, 0, j, (i, j) => ascendingDefined(numbers[i], numbers[j])); + i = greatest(index.subarray(0, i + 1), (i) => numbers[i]); + return i >= 0 ? i : -1; +} + +function thresholdFreedmanDiaconis(values, min, max) { + const c = count$1(values), d = quantile$1(values, 0.75) - quantile$1(values, 0.25); + return c && d ? Math.ceil((max - min) / (2 * d * Math.pow(c, -1 / 3))) : 1; +} + +function thresholdScott(values, min, max) { + const c = count$1(values), d = deviation(values); + return c && d ? Math.ceil((max - min) * Math.cbrt(c) / (3.49 * d)) : 1; +} + +function mean(values, valueof) { + let count = 0; + let sum = 0; + if (valueof === undefined) { + for (let value of values) { + if (value != null && (value = +value) >= value) { + ++count, sum += value; + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { + ++count, sum += value; + } + } + } + if (count) return sum / count; +} + +function median(values, valueof) { + return quantile$1(values, 0.5, valueof); +} + +function medianIndex(values, valueof) { + return quantileIndex(values, 0.5, valueof); +} + +function* flatten(arrays) { + for (const array of arrays) { + yield* array; + } +} + +function merge(arrays) { + return Array.from(flatten(arrays)); +} + +function mode(values, valueof) { + const counts = new InternMap(); + if (valueof === undefined) { + for (let value of values) { + if (value != null && value >= value) { + counts.set(value, (counts.get(value) || 0) + 1); + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null && value >= value) { + counts.set(value, (counts.get(value) || 0) + 1); + } + } + } + let modeValue; + let modeCount = 0; + for (const [value, count] of counts) { + if (count > modeCount) { + modeCount = count; + modeValue = value; + } + } + return modeValue; +} + +function pairs(values, pairof = pair) { + const pairs = []; + let previous; + let first = false; + for (const value of values) { + if (first) pairs.push(pairof(previous, value)); + previous = value; + first = true; + } + return pairs; +} + +function pair(a, b) { + return [a, b]; +} + +function range$2(start, stop, step) { + start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step; + + var i = -1, + n = Math.max(0, Math.ceil((stop - start) / step)) | 0, + range = new Array(n); + + while (++i < n) { + range[i] = start + i * step; + } + + return range; +} + +function rank(values, valueof = ascending$3) { + if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); + let V = Array.from(values); + const R = new Float64Array(V.length); + if (valueof.length !== 2) V = V.map(valueof), valueof = ascending$3; + const compareIndex = (i, j) => valueof(V[i], V[j]); + let k, r; + values = Uint32Array.from(V, (_, i) => i); + // Risky chaining due to Safari 14 https://github.com/d3/d3-array/issues/123 + values.sort(valueof === ascending$3 ? (i, j) => ascendingDefined(V[i], V[j]) : compareDefined(compareIndex)); + values.forEach((j, i) => { + const c = compareIndex(j, k === undefined ? j : k); + if (c >= 0) { + if (k === undefined || c > 0) k = j, r = i; + R[j] = r; + } else { + R[j] = NaN; + } + }); + return R; +} + +function least(values, compare = ascending$3) { + let min; + let defined = false; + if (compare.length === 1) { + let minValue; + for (const element of values) { + const value = compare(element); + if (defined + ? ascending$3(value, minValue) < 0 + : ascending$3(value, value) === 0) { + min = element; + minValue = value; + defined = true; + } + } + } else { + for (const value of values) { + if (defined + ? compare(value, min) < 0 + : compare(value, value) === 0) { + min = value; + defined = true; + } + } + } + return min; +} + +function leastIndex(values, compare = ascending$3) { + if (compare.length === 1) return minIndex(values, compare); + let minValue; + let min = -1; + let index = -1; + for (const value of values) { + ++index; + if (min < 0 + ? compare(value, value) === 0 + : compare(value, minValue) < 0) { + minValue = value; + min = index; + } + } + return min; +} + +function greatestIndex(values, compare = ascending$3) { + if (compare.length === 1) return maxIndex(values, compare); + let maxValue; + let max = -1; + let index = -1; + for (const value of values) { + ++index; + if (max < 0 + ? compare(value, value) === 0 + : compare(value, maxValue) > 0) { + maxValue = value; + max = index; + } + } + return max; +} + +function scan(values, compare) { + const index = leastIndex(values, compare); + return index < 0 ? undefined : index; +} + +var shuffle$1 = shuffler(Math.random); + +function shuffler(random) { + return function shuffle(array, i0 = 0, i1 = array.length) { + let m = i1 - (i0 = +i0); + while (m) { + const i = random() * m-- | 0, t = array[m + i0]; + array[m + i0] = array[i + i0]; + array[i + i0] = t; + } + return array; + }; +} + +function sum$2(values, valueof) { + let sum = 0; + if (valueof === undefined) { + for (let value of values) { + if (value = +value) { + sum += value; + } + } + } else { + let index = -1; + for (let value of values) { + if (value = +valueof(value, ++index, values)) { + sum += value; + } + } + } + return sum; +} + +function transpose(matrix) { + if (!(n = matrix.length)) return []; + for (var i = -1, m = min$2(matrix, length$2), transpose = new Array(m); ++i < m;) { + for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n;) { + row[j] = matrix[j][i]; + } + } + return transpose; +} + +function length$2(d) { + return d.length; +} + +function zip() { + return transpose(arguments); +} + +function every(values, test) { + if (typeof test !== "function") throw new TypeError("test is not a function"); + let index = -1; + for (const value of values) { + if (!test(value, ++index, values)) { + return false; + } + } + return true; +} + +function some(values, test) { + if (typeof test !== "function") throw new TypeError("test is not a function"); + let index = -1; + for (const value of values) { + if (test(value, ++index, values)) { + return true; + } + } + return false; +} + +function filter$1(values, test) { + if (typeof test !== "function") throw new TypeError("test is not a function"); + const array = []; + let index = -1; + for (const value of values) { + if (test(value, ++index, values)) { + array.push(value); + } + } + return array; +} + +function map$1(values, mapper) { + if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); + if (typeof mapper !== "function") throw new TypeError("mapper is not a function"); + return Array.from(values, (value, index) => mapper(value, index, values)); +} + +function reduce(values, reducer, value) { + if (typeof reducer !== "function") throw new TypeError("reducer is not a function"); + const iterator = values[Symbol.iterator](); + let done, next, index = -1; + if (arguments.length < 3) { + ({done, value} = iterator.next()); + if (done) return; + ++index; + } + while (({done, value: next} = iterator.next()), !done) { + value = reducer(value, next, ++index, values); + } + return value; +} + +function reverse$1(values) { + if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); + return Array.from(values).reverse(); +} + +function difference(values, ...others) { + values = new InternSet(values); + for (const other of others) { + for (const value of other) { + values.delete(value); + } + } + return values; +} + +function disjoint(values, other) { + const iterator = other[Symbol.iterator](), set = new InternSet(); + for (const v of values) { + if (set.has(v)) return false; + let value, done; + while (({value, done} = iterator.next())) { + if (done) break; + if (Object.is(v, value)) return false; + set.add(value); + } + } + return true; +} + +function intersection(values, ...others) { + values = new InternSet(values); + others = others.map(set$2); + out: for (const value of values) { + for (const other of others) { + if (!other.has(value)) { + values.delete(value); + continue out; + } + } + } + return values; +} + +function set$2(values) { + return values instanceof InternSet ? values : new InternSet(values); +} + +function superset(values, other) { + const iterator = values[Symbol.iterator](), set = new Set(); + for (const o of other) { + const io = intern(o); + if (set.has(io)) continue; + let value, done; + while (({value, done} = iterator.next())) { + if (done) return false; + const ivalue = intern(value); + set.add(ivalue); + if (Object.is(io, ivalue)) break; + } + } + return true; +} + +function intern(value) { + return value !== null && typeof value === "object" ? value.valueOf() : value; +} + +function subset(values, other) { + return superset(other, values); +} + +function union(...others) { + const set = new InternSet(); + for (const other of others) { + for (const o of other) { + set.add(o); + } + } + return set; +} + +function identity$8(x) { + return x; +} + +var top = 1, + right = 2, + bottom = 3, + left = 4, + epsilon$6 = 1e-6; + +function translateX(x) { + return "translate(" + x + ",0)"; +} + +function translateY(y) { + return "translate(0," + y + ")"; +} + +function number$2(scale) { + return d => +scale(d); +} + +function center$1(scale, offset) { + offset = Math.max(0, scale.bandwidth() - offset * 2) / 2; + if (scale.round()) offset = Math.round(offset); + return d => +scale(d) + offset; +} + +function entering() { + return !this.__axis; +} + +function axis(orient, scale) { + var tickArguments = [], + tickValues = null, + tickFormat = null, + tickSizeInner = 6, + tickSizeOuter = 6, + tickPadding = 3, + offset = typeof window !== "undefined" && window.devicePixelRatio > 1 ? 0 : 0.5, + k = orient === top || orient === left ? -1 : 1, + x = orient === left || orient === right ? "x" : "y", + transform = orient === top || orient === bottom ? translateX : translateY; + + function axis(context) { + var values = tickValues == null ? (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) : tickValues, + format = tickFormat == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : identity$8) : tickFormat, + spacing = Math.max(tickSizeInner, 0) + tickPadding, + range = scale.range(), + range0 = +range[0] + offset, + range1 = +range[range.length - 1] + offset, + position = (scale.bandwidth ? center$1 : number$2)(scale.copy(), offset), + selection = context.selection ? context.selection() : context, + path = selection.selectAll(".domain").data([null]), + tick = selection.selectAll(".tick").data(values, scale).order(), + tickExit = tick.exit(), + tickEnter = tick.enter().append("g").attr("class", "tick"), + line = tick.select("line"), + text = tick.select("text"); + + path = path.merge(path.enter().insert("path", ".tick") + .attr("class", "domain") + .attr("stroke", "currentColor")); + + tick = tick.merge(tickEnter); + + line = line.merge(tickEnter.append("line") + .attr("stroke", "currentColor") + .attr(x + "2", k * tickSizeInner)); + + text = text.merge(tickEnter.append("text") + .attr("fill", "currentColor") + .attr(x, k * spacing) + .attr("dy", orient === top ? "0em" : orient === bottom ? "0.71em" : "0.32em")); + + if (context !== selection) { + path = path.transition(context); + tick = tick.transition(context); + line = line.transition(context); + text = text.transition(context); + + tickExit = tickExit.transition(context) + .attr("opacity", epsilon$6) + .attr("transform", function(d) { return isFinite(d = position(d)) ? transform(d + offset) : this.getAttribute("transform"); }); + + tickEnter + .attr("opacity", epsilon$6) + .attr("transform", function(d) { var p = this.parentNode.__axis; return transform((p && isFinite(p = p(d)) ? p : position(d)) + offset); }); + } + + tickExit.remove(); + + path + .attr("d", orient === left || orient === right + ? (tickSizeOuter ? "M" + k * tickSizeOuter + "," + range0 + "H" + offset + "V" + range1 + "H" + k * tickSizeOuter : "M" + offset + "," + range0 + "V" + range1) + : (tickSizeOuter ? "M" + range0 + "," + k * tickSizeOuter + "V" + offset + "H" + range1 + "V" + k * tickSizeOuter : "M" + range0 + "," + offset + "H" + range1)); + + tick + .attr("opacity", 1) + .attr("transform", function(d) { return transform(position(d) + offset); }); + + line + .attr(x + "2", k * tickSizeInner); + + text + .attr(x, k * spacing) + .text(format); + + selection.filter(entering) + .attr("fill", "none") + .attr("font-size", 10) + .attr("font-family", "sans-serif") + .attr("text-anchor", orient === right ? "start" : orient === left ? "end" : "middle"); + + selection + .each(function() { this.__axis = position; }); + } + + axis.scale = function(_) { + return arguments.length ? (scale = _, axis) : scale; + }; + + axis.ticks = function() { + return tickArguments = Array.from(arguments), axis; + }; + + axis.tickArguments = function(_) { + return arguments.length ? (tickArguments = _ == null ? [] : Array.from(_), axis) : tickArguments.slice(); + }; + + axis.tickValues = function(_) { + return arguments.length ? (tickValues = _ == null ? null : Array.from(_), axis) : tickValues && tickValues.slice(); + }; + + axis.tickFormat = function(_) { + return arguments.length ? (tickFormat = _, axis) : tickFormat; + }; + + axis.tickSize = function(_) { + return arguments.length ? (tickSizeInner = tickSizeOuter = +_, axis) : tickSizeInner; + }; + + axis.tickSizeInner = function(_) { + return arguments.length ? (tickSizeInner = +_, axis) : tickSizeInner; + }; + + axis.tickSizeOuter = function(_) { + return arguments.length ? (tickSizeOuter = +_, axis) : tickSizeOuter; + }; + + axis.tickPadding = function(_) { + return arguments.length ? (tickPadding = +_, axis) : tickPadding; + }; + + axis.offset = function(_) { + return arguments.length ? (offset = +_, axis) : offset; + }; + + return axis; +} + +function axisTop(scale) { + return axis(top, scale); +} + +function axisRight(scale) { + return axis(right, scale); +} + +function axisBottom(scale) { + return axis(bottom, scale); +} + +function axisLeft(scale) { + return axis(left, scale); +} + +var noop$3 = {value: () => {}}; + +function dispatch() { + for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) { + if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t); + _[t] = []; + } + return new Dispatch(_); +} + +function Dispatch(_) { + this._ = _; +} + +function parseTypenames$1(typenames, types) { + return typenames.trim().split(/^|\s+/).map(function(t) { + var name = "", i = t.indexOf("."); + if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); + if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t); + return {type: t, name: name}; + }); +} + +Dispatch.prototype = dispatch.prototype = { + constructor: Dispatch, + on: function(typename, callback) { + var _ = this._, + T = parseTypenames$1(typename + "", _), + t, + i = -1, + n = T.length; + + // If no callback was specified, return the callback of the given type and name. + if (arguments.length < 2) { + while (++i < n) if ((t = (typename = T[i]).type) && (t = get$1(_[t], typename.name))) return t; + return; + } + + // If a type was specified, set the callback for the given type and name. + // Otherwise, if a null callback was specified, remove callbacks of the given name. + if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback); + while (++i < n) { + if (t = (typename = T[i]).type) _[t] = set$1(_[t], typename.name, callback); + else if (callback == null) for (t in _) _[t] = set$1(_[t], typename.name, null); + } + + return this; + }, + copy: function() { + var copy = {}, _ = this._; + for (var t in _) copy[t] = _[t].slice(); + return new Dispatch(copy); + }, + call: function(type, that) { + if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2]; + if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); + for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); + }, + apply: function(type, that, args) { + if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); + for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); + } +}; + +function get$1(type, name) { + for (var i = 0, n = type.length, c; i < n; ++i) { + if ((c = type[i]).name === name) { + return c.value; + } + } +} + +function set$1(type, name, callback) { + for (var i = 0, n = type.length; i < n; ++i) { + if (type[i].name === name) { + type[i] = noop$3, type = type.slice(0, i).concat(type.slice(i + 1)); + break; + } + } + if (callback != null) type.push({name: name, value: callback}); + return type; +} + +var xhtml = "http://www.w3.org/1999/xhtml"; + +var namespaces = { + svg: "http://www.w3.org/2000/svg", + xhtml: xhtml, + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" +}; + +function namespace(name) { + var prefix = name += "", i = prefix.indexOf(":"); + if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); + return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name; // eslint-disable-line no-prototype-builtins +} + +function creatorInherit(name) { + return function() { + var document = this.ownerDocument, + uri = this.namespaceURI; + return uri === xhtml && document.documentElement.namespaceURI === xhtml + ? document.createElement(name) + : document.createElementNS(uri, name); + }; +} + +function creatorFixed(fullname) { + return function() { + return this.ownerDocument.createElementNS(fullname.space, fullname.local); + }; +} + +function creator(name) { + var fullname = namespace(name); + return (fullname.local + ? creatorFixed + : creatorInherit)(fullname); +} + +function none$2() {} + +function selector(selector) { + return selector == null ? none$2 : function() { + return this.querySelector(selector); + }; +} + +function selection_select(select) { + if (typeof select !== "function") select = selector(select); + + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { + if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + subgroup[i] = subnode; + } + } + } + + return new Selection$1(subgroups, this._parents); +} + +// Given something array like (or null), returns something that is strictly an +// array. This is used to ensure that array-like objects passed to d3.selectAll +// or selection.selectAll are converted into proper arrays when creating a +// selection; we don’t ever want to create a selection backed by a live +// HTMLCollection or NodeList. However, note that selection.selectAll will use a +// static NodeList as a group, since it safely derived from querySelectorAll. +function array$4(x) { + return x == null ? [] : Array.isArray(x) ? x : Array.from(x); +} + +function empty$1() { + return []; +} + +function selectorAll(selector) { + return selector == null ? empty$1 : function() { + return this.querySelectorAll(selector); + }; +} + +function arrayAll(select) { + return function() { + return array$4(select.apply(this, arguments)); + }; +} + +function selection_selectAll(select) { + if (typeof select === "function") select = arrayAll(select); + else select = selectorAll(select); + + for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + subgroups.push(select.call(node, node.__data__, i, group)); + parents.push(node); + } + } + } + + return new Selection$1(subgroups, parents); +} + +function matcher(selector) { + return function() { + return this.matches(selector); + }; +} + +function childMatcher(selector) { + return function(node) { + return node.matches(selector); + }; +} + +var find$1 = Array.prototype.find; + +function childFind(match) { + return function() { + return find$1.call(this.children, match); + }; +} + +function childFirst() { + return this.firstElementChild; +} + +function selection_selectChild(match) { + return this.select(match == null ? childFirst + : childFind(typeof match === "function" ? match : childMatcher(match))); +} + +var filter = Array.prototype.filter; + +function children() { + return Array.from(this.children); +} + +function childrenFilter(match) { + return function() { + return filter.call(this.children, match); + }; +} + +function selection_selectChildren(match) { + return this.selectAll(match == null ? children + : childrenFilter(typeof match === "function" ? match : childMatcher(match))); +} + +function selection_filter(match) { + if (typeof match !== "function") match = matcher(match); + + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { + if ((node = group[i]) && match.call(node, node.__data__, i, group)) { + subgroup.push(node); + } + } + } + + return new Selection$1(subgroups, this._parents); +} + +function sparse(update) { + return new Array(update.length); +} + +function selection_enter() { + return new Selection$1(this._enter || this._groups.map(sparse), this._parents); +} + +function EnterNode(parent, datum) { + this.ownerDocument = parent.ownerDocument; + this.namespaceURI = parent.namespaceURI; + this._next = null; + this._parent = parent; + this.__data__ = datum; +} + +EnterNode.prototype = { + constructor: EnterNode, + appendChild: function(child) { return this._parent.insertBefore(child, this._next); }, + insertBefore: function(child, next) { return this._parent.insertBefore(child, next); }, + querySelector: function(selector) { return this._parent.querySelector(selector); }, + querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); } +}; + +function constant$a(x) { + return function() { + return x; + }; +} + +function bindIndex(parent, group, enter, update, exit, data) { + var i = 0, + node, + groupLength = group.length, + dataLength = data.length; + + // Put any non-null nodes that fit into update. + // Put any null nodes into enter. + // Put any remaining data into enter. + for (; i < dataLength; ++i) { + if (node = group[i]) { + node.__data__ = data[i]; + update[i] = node; + } else { + enter[i] = new EnterNode(parent, data[i]); + } + } + + // Put any non-null nodes that don’t fit into exit. + for (; i < groupLength; ++i) { + if (node = group[i]) { + exit[i] = node; + } + } +} + +function bindKey(parent, group, enter, update, exit, data, key) { + var i, + node, + nodeByKeyValue = new Map, + groupLength = group.length, + dataLength = data.length, + keyValues = new Array(groupLength), + keyValue; + + // Compute the key for each node. + // If multiple nodes have the same key, the duplicates are added to exit. + for (i = 0; i < groupLength; ++i) { + if (node = group[i]) { + keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + ""; + if (nodeByKeyValue.has(keyValue)) { + exit[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + } + } + + // Compute the key for each datum. + // If there a node associated with this key, join and add it to update. + // If there is not (or the key is a duplicate), add it to enter. + for (i = 0; i < dataLength; ++i) { + keyValue = key.call(parent, data[i], i, data) + ""; + if (node = nodeByKeyValue.get(keyValue)) { + update[i] = node; + node.__data__ = data[i]; + nodeByKeyValue.delete(keyValue); + } else { + enter[i] = new EnterNode(parent, data[i]); + } + } + + // Add any remaining nodes that were not bound to data to exit. + for (i = 0; i < groupLength; ++i) { + if ((node = group[i]) && (nodeByKeyValue.get(keyValues[i]) === node)) { + exit[i] = node; + } + } +} + +function datum(node) { + return node.__data__; +} + +function selection_data(value, key) { + if (!arguments.length) return Array.from(this, datum); + + var bind = key ? bindKey : bindIndex, + parents = this._parents, + groups = this._groups; + + if (typeof value !== "function") value = constant$a(value); + + for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) { + var parent = parents[j], + group = groups[j], + groupLength = group.length, + data = arraylike(value.call(parent, parent && parent.__data__, j, parents)), + dataLength = data.length, + enterGroup = enter[j] = new Array(dataLength), + updateGroup = update[j] = new Array(dataLength), + exitGroup = exit[j] = new Array(groupLength); + + bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); + + // Now connect the enter nodes to their following update node, such that + // appendChild can insert the materialized enter node before this node, + // rather than at the end of the parent node. + for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) { + if (previous = enterGroup[i0]) { + if (i0 >= i1) i1 = i0 + 1; + while (!(next = updateGroup[i1]) && ++i1 < dataLength); + previous._next = next || null; + } + } + } + + update = new Selection$1(update, parents); + update._enter = enter; + update._exit = exit; + return update; +} + +// Given some data, this returns an array-like view of it: an object that +// exposes a length property and allows numeric indexing. Note that unlike +// selectAll, this isn’t worried about “live” collections because the resulting +// array will only be used briefly while data is being bound. (It is possible to +// cause the data to change while iterating by using a key function, but please +// don’t; we’d rather avoid a gratuitous copy.) +function arraylike(data) { + return typeof data === "object" && "length" in data + ? data // Array, TypedArray, NodeList, array-like + : Array.from(data); // Map, Set, iterable, string, or anything else +} + +function selection_exit() { + return new Selection$1(this._exit || this._groups.map(sparse), this._parents); +} + +function selection_join(onenter, onupdate, onexit) { + var enter = this.enter(), update = this, exit = this.exit(); + if (typeof onenter === "function") { + enter = onenter(enter); + if (enter) enter = enter.selection(); + } else { + enter = enter.append(onenter + ""); + } + if (onupdate != null) { + update = onupdate(update); + if (update) update = update.selection(); + } + if (onexit == null) exit.remove(); else onexit(exit); + return enter && update ? enter.merge(update).order() : update; +} + +function selection_merge(context) { + var selection = context.selection ? context.selection() : context; + + for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { + for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { + if (node = group0[i] || group1[i]) { + merge[i] = node; + } + } + } + + for (; j < m0; ++j) { + merges[j] = groups0[j]; + } + + return new Selection$1(merges, this._parents); +} + +function selection_order() { + + for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) { + for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) { + if (node = group[i]) { + if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + + return this; +} + +function selection_sort(compare) { + if (!compare) compare = ascending$2; + + function compareNode(a, b) { + return a && b ? compare(a.__data__, b.__data__) : !a - !b; + } + + for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) { + if (node = group[i]) { + sortgroup[i] = node; + } + } + sortgroup.sort(compareNode); + } + + return new Selection$1(sortgroups, this._parents).order(); +} + +function ascending$2(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; +} + +function selection_call() { + var callback = arguments[0]; + arguments[0] = this; + callback.apply(null, arguments); + return this; +} + +function selection_nodes() { + return Array.from(this); +} + +function selection_node() { + + for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { + for (var group = groups[j], i = 0, n = group.length; i < n; ++i) { + var node = group[i]; + if (node) return node; + } + } + + return null; +} + +function selection_size() { + let size = 0; + for (const node of this) ++size; // eslint-disable-line no-unused-vars + return size; +} + +function selection_empty() { + return !this.node(); +} + +function selection_each(callback) { + + for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { + if (node = group[i]) callback.call(node, node.__data__, i, group); + } + } + + return this; +} + +function attrRemove$1(name) { + return function() { + this.removeAttribute(name); + }; +} + +function attrRemoveNS$1(fullname) { + return function() { + this.removeAttributeNS(fullname.space, fullname.local); + }; +} + +function attrConstant$1(name, value) { + return function() { + this.setAttribute(name, value); + }; +} + +function attrConstantNS$1(fullname, value) { + return function() { + this.setAttributeNS(fullname.space, fullname.local, value); + }; +} + +function attrFunction$1(name, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.removeAttribute(name); + else this.setAttribute(name, v); + }; +} + +function attrFunctionNS$1(fullname, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.removeAttributeNS(fullname.space, fullname.local); + else this.setAttributeNS(fullname.space, fullname.local, v); + }; +} + +function selection_attr(name, value) { + var fullname = namespace(name); + + if (arguments.length < 2) { + var node = this.node(); + return fullname.local + ? node.getAttributeNS(fullname.space, fullname.local) + : node.getAttribute(fullname); + } + + return this.each((value == null + ? (fullname.local ? attrRemoveNS$1 : attrRemove$1) : (typeof value === "function" + ? (fullname.local ? attrFunctionNS$1 : attrFunction$1) + : (fullname.local ? attrConstantNS$1 : attrConstant$1)))(fullname, value)); +} + +function defaultView(node) { + return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node + || (node.document && node) // node is a Window + || node.defaultView; // node is a Document +} + +function styleRemove$1(name) { + return function() { + this.style.removeProperty(name); + }; +} + +function styleConstant$1(name, value, priority) { + return function() { + this.style.setProperty(name, value, priority); + }; +} + +function styleFunction$1(name, value, priority) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.style.removeProperty(name); + else this.style.setProperty(name, v, priority); + }; +} + +function selection_style(name, value, priority) { + return arguments.length > 1 + ? this.each((value == null + ? styleRemove$1 : typeof value === "function" + ? styleFunction$1 + : styleConstant$1)(name, value, priority == null ? "" : priority)) + : styleValue(this.node(), name); +} + +function styleValue(node, name) { + return node.style.getPropertyValue(name) + || defaultView(node).getComputedStyle(node, null).getPropertyValue(name); +} + +function propertyRemove(name) { + return function() { + delete this[name]; + }; +} + +function propertyConstant(name, value) { + return function() { + this[name] = value; + }; +} + +function propertyFunction(name, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) delete this[name]; + else this[name] = v; + }; +} + +function selection_property(name, value) { + return arguments.length > 1 + ? this.each((value == null + ? propertyRemove : typeof value === "function" + ? propertyFunction + : propertyConstant)(name, value)) + : this.node()[name]; +} + +function classArray(string) { + return string.trim().split(/^|\s+/); +} + +function classList(node) { + return node.classList || new ClassList(node); +} + +function ClassList(node) { + this._node = node; + this._names = classArray(node.getAttribute("class") || ""); +} + +ClassList.prototype = { + add: function(name) { + var i = this._names.indexOf(name); + if (i < 0) { + this._names.push(name); + this._node.setAttribute("class", this._names.join(" ")); + } + }, + remove: function(name) { + var i = this._names.indexOf(name); + if (i >= 0) { + this._names.splice(i, 1); + this._node.setAttribute("class", this._names.join(" ")); + } + }, + contains: function(name) { + return this._names.indexOf(name) >= 0; + } +}; + +function classedAdd(node, names) { + var list = classList(node), i = -1, n = names.length; + while (++i < n) list.add(names[i]); +} + +function classedRemove(node, names) { + var list = classList(node), i = -1, n = names.length; + while (++i < n) list.remove(names[i]); +} + +function classedTrue(names) { + return function() { + classedAdd(this, names); + }; +} + +function classedFalse(names) { + return function() { + classedRemove(this, names); + }; +} + +function classedFunction(names, value) { + return function() { + (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names); + }; +} + +function selection_classed(name, value) { + var names = classArray(name + ""); + + if (arguments.length < 2) { + var list = classList(this.node()), i = -1, n = names.length; + while (++i < n) if (!list.contains(names[i])) return false; + return true; + } + + return this.each((typeof value === "function" + ? classedFunction : value + ? classedTrue + : classedFalse)(names, value)); +} + +function textRemove() { + this.textContent = ""; +} + +function textConstant$1(value) { + return function() { + this.textContent = value; + }; +} + +function textFunction$1(value) { + return function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + }; +} + +function selection_text(value) { + return arguments.length + ? this.each(value == null + ? textRemove : (typeof value === "function" + ? textFunction$1 + : textConstant$1)(value)) + : this.node().textContent; +} + +function htmlRemove() { + this.innerHTML = ""; +} + +function htmlConstant(value) { + return function() { + this.innerHTML = value; + }; +} + +function htmlFunction(value) { + return function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + }; +} + +function selection_html(value) { + return arguments.length + ? this.each(value == null + ? htmlRemove : (typeof value === "function" + ? htmlFunction + : htmlConstant)(value)) + : this.node().innerHTML; +} + +function raise() { + if (this.nextSibling) this.parentNode.appendChild(this); +} + +function selection_raise() { + return this.each(raise); +} + +function lower() { + if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild); +} + +function selection_lower() { + return this.each(lower); +} + +function selection_append(name) { + var create = typeof name === "function" ? name : creator(name); + return this.select(function() { + return this.appendChild(create.apply(this, arguments)); + }); +} + +function constantNull() { + return null; +} + +function selection_insert(name, before) { + var create = typeof name === "function" ? name : creator(name), + select = before == null ? constantNull : typeof before === "function" ? before : selector(before); + return this.select(function() { + return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null); + }); +} + +function remove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); +} + +function selection_remove() { + return this.each(remove); +} + +function selection_cloneShallow() { + var clone = this.cloneNode(false), parent = this.parentNode; + return parent ? parent.insertBefore(clone, this.nextSibling) : clone; +} + +function selection_cloneDeep() { + var clone = this.cloneNode(true), parent = this.parentNode; + return parent ? parent.insertBefore(clone, this.nextSibling) : clone; +} + +function selection_clone(deep) { + return this.select(deep ? selection_cloneDeep : selection_cloneShallow); +} + +function selection_datum(value) { + return arguments.length + ? this.property("__data__", value) + : this.node().__data__; +} + +function contextListener(listener) { + return function(event) { + listener.call(this, event, this.__data__); + }; +} + +function parseTypenames(typenames) { + return typenames.trim().split(/^|\s+/).map(function(t) { + var name = "", i = t.indexOf("."); + if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); + return {type: t, name: name}; + }); +} + +function onRemove(typename) { + return function() { + var on = this.__on; + if (!on) return; + for (var j = 0, i = -1, m = on.length, o; j < m; ++j) { + if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) { + this.removeEventListener(o.type, o.listener, o.options); + } else { + on[++i] = o; + } + } + if (++i) on.length = i; + else delete this.__on; + }; +} + +function onAdd(typename, value, options) { + return function() { + var on = this.__on, o, listener = contextListener(value); + if (on) for (var j = 0, m = on.length; j < m; ++j) { + if ((o = on[j]).type === typename.type && o.name === typename.name) { + this.removeEventListener(o.type, o.listener, o.options); + this.addEventListener(o.type, o.listener = listener, o.options = options); + o.value = value; + return; + } + } + this.addEventListener(typename.type, listener, options); + o = {type: typename.type, name: typename.name, value: value, listener: listener, options: options}; + if (!on) this.__on = [o]; + else on.push(o); + }; +} + +function selection_on(typename, value, options) { + var typenames = parseTypenames(typename + ""), i, n = typenames.length, t; + + if (arguments.length < 2) { + var on = this.node().__on; + if (on) for (var j = 0, m = on.length, o; j < m; ++j) { + for (i = 0, o = on[j]; i < n; ++i) { + if ((t = typenames[i]).type === o.type && t.name === o.name) { + return o.value; + } + } + } + return; + } + + on = value ? onAdd : onRemove; + for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options)); + return this; +} + +function dispatchEvent(node, type, params) { + var window = defaultView(node), + event = window.CustomEvent; + + if (typeof event === "function") { + event = new event(type, params); + } else { + event = window.document.createEvent("Event"); + if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail; + else event.initEvent(type, false, false); + } + + node.dispatchEvent(event); +} + +function dispatchConstant(type, params) { + return function() { + return dispatchEvent(this, type, params); + }; +} + +function dispatchFunction(type, params) { + return function() { + return dispatchEvent(this, type, params.apply(this, arguments)); + }; +} + +function selection_dispatch(type, params) { + return this.each((typeof params === "function" + ? dispatchFunction + : dispatchConstant)(type, params)); +} + +function* selection_iterator() { + for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { + if (node = group[i]) yield node; + } + } +} + +var root$1 = [null]; + +function Selection$1(groups, parents) { + this._groups = groups; + this._parents = parents; +} + +function selection() { + return new Selection$1([[document.documentElement]], root$1); +} + +function selection_selection() { + return this; +} + +Selection$1.prototype = selection.prototype = { + constructor: Selection$1, + select: selection_select, + selectAll: selection_selectAll, + selectChild: selection_selectChild, + selectChildren: selection_selectChildren, + filter: selection_filter, + data: selection_data, + enter: selection_enter, + exit: selection_exit, + join: selection_join, + merge: selection_merge, + selection: selection_selection, + order: selection_order, + sort: selection_sort, + call: selection_call, + nodes: selection_nodes, + node: selection_node, + size: selection_size, + empty: selection_empty, + each: selection_each, + attr: selection_attr, + style: selection_style, + property: selection_property, + classed: selection_classed, + text: selection_text, + html: selection_html, + raise: selection_raise, + lower: selection_lower, + append: selection_append, + insert: selection_insert, + remove: selection_remove, + clone: selection_clone, + datum: selection_datum, + on: selection_on, + dispatch: selection_dispatch, + [Symbol.iterator]: selection_iterator +}; + +function select(selector) { + return typeof selector === "string" + ? new Selection$1([[document.querySelector(selector)]], [document.documentElement]) + : new Selection$1([[selector]], root$1); +} + +function create$1(name) { + return select(creator(name).call(document.documentElement)); +} + +var nextId = 0; + +function local$1() { + return new Local; +} + +function Local() { + this._ = "@" + (++nextId).toString(36); +} + +Local.prototype = local$1.prototype = { + constructor: Local, + get: function(node) { + var id = this._; + while (!(id in node)) if (!(node = node.parentNode)) return; + return node[id]; + }, + set: function(node, value) { + return node[this._] = value; + }, + remove: function(node) { + return this._ in node && delete node[this._]; + }, + toString: function() { + return this._; + } +}; + +function sourceEvent(event) { + let sourceEvent; + while (sourceEvent = event.sourceEvent) event = sourceEvent; + return event; +} + +function pointer(event, node) { + event = sourceEvent(event); + if (node === undefined) node = event.currentTarget; + if (node) { + var svg = node.ownerSVGElement || node; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + point.x = event.clientX, point.y = event.clientY; + point = point.matrixTransform(node.getScreenCTM().inverse()); + return [point.x, point.y]; + } + if (node.getBoundingClientRect) { + var rect = node.getBoundingClientRect(); + return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop]; + } + } + return [event.pageX, event.pageY]; +} + +function pointers(events, node) { + if (events.target) { // i.e., instanceof Event, not TouchList or iterable + events = sourceEvent(events); + if (node === undefined) node = events.currentTarget; + events = events.touches || [events]; + } + return Array.from(events, event => pointer(event, node)); +} + +function selectAll(selector) { + return typeof selector === "string" + ? new Selection$1([document.querySelectorAll(selector)], [document.documentElement]) + : new Selection$1([array$4(selector)], root$1); +} + +// These are typically used in conjunction with noevent to ensure that we can +// preventDefault on the event. +const nonpassive = {passive: false}; +const nonpassivecapture = {capture: true, passive: false}; + +function nopropagation$2(event) { + event.stopImmediatePropagation(); +} + +function noevent$2(event) { + event.preventDefault(); + event.stopImmediatePropagation(); +} + +function dragDisable(view) { + var root = view.document.documentElement, + selection = select(view).on("dragstart.drag", noevent$2, nonpassivecapture); + if ("onselectstart" in root) { + selection.on("selectstart.drag", noevent$2, nonpassivecapture); + } else { + root.__noselect = root.style.MozUserSelect; + root.style.MozUserSelect = "none"; + } +} + +function yesdrag(view, noclick) { + var root = view.document.documentElement, + selection = select(view).on("dragstart.drag", null); + if (noclick) { + selection.on("click.drag", noevent$2, nonpassivecapture); + setTimeout(function() { selection.on("click.drag", null); }, 0); + } + if ("onselectstart" in root) { + selection.on("selectstart.drag", null); + } else { + root.style.MozUserSelect = root.__noselect; + delete root.__noselect; + } +} + +var constant$9 = x => () => x; + +function DragEvent(type, { + sourceEvent, + subject, + target, + identifier, + active, + x, y, dx, dy, + dispatch +}) { + Object.defineProperties(this, { + type: {value: type, enumerable: true, configurable: true}, + sourceEvent: {value: sourceEvent, enumerable: true, configurable: true}, + subject: {value: subject, enumerable: true, configurable: true}, + target: {value: target, enumerable: true, configurable: true}, + identifier: {value: identifier, enumerable: true, configurable: true}, + active: {value: active, enumerable: true, configurable: true}, + x: {value: x, enumerable: true, configurable: true}, + y: {value: y, enumerable: true, configurable: true}, + dx: {value: dx, enumerable: true, configurable: true}, + dy: {value: dy, enumerable: true, configurable: true}, + _: {value: dispatch} + }); +} + +DragEvent.prototype.on = function() { + var value = this._.on.apply(this._, arguments); + return value === this._ ? this : value; +}; + +// Ignore right-click, since that should open the context menu. +function defaultFilter$2(event) { + return !event.ctrlKey && !event.button; +} + +function defaultContainer() { + return this.parentNode; +} + +function defaultSubject(event, d) { + return d == null ? {x: event.x, y: event.y} : d; +} + +function defaultTouchable$2() { + return navigator.maxTouchPoints || ("ontouchstart" in this); +} + +function drag() { + var filter = defaultFilter$2, + container = defaultContainer, + subject = defaultSubject, + touchable = defaultTouchable$2, + gestures = {}, + listeners = dispatch("start", "drag", "end"), + active = 0, + mousedownx, + mousedowny, + mousemoving, + touchending, + clickDistance2 = 0; + + function drag(selection) { + selection + .on("mousedown.drag", mousedowned) + .filter(touchable) + .on("touchstart.drag", touchstarted) + .on("touchmove.drag", touchmoved, nonpassive) + .on("touchend.drag touchcancel.drag", touchended) + .style("touch-action", "none") + .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)"); + } + + function mousedowned(event, d) { + if (touchending || !filter.call(this, event, d)) return; + var gesture = beforestart(this, container.call(this, event, d), event, d, "mouse"); + if (!gesture) return; + select(event.view) + .on("mousemove.drag", mousemoved, nonpassivecapture) + .on("mouseup.drag", mouseupped, nonpassivecapture); + dragDisable(event.view); + nopropagation$2(event); + mousemoving = false; + mousedownx = event.clientX; + mousedowny = event.clientY; + gesture("start", event); + } + + function mousemoved(event) { + noevent$2(event); + if (!mousemoving) { + var dx = event.clientX - mousedownx, dy = event.clientY - mousedowny; + mousemoving = dx * dx + dy * dy > clickDistance2; + } + gestures.mouse("drag", event); + } + + function mouseupped(event) { + select(event.view).on("mousemove.drag mouseup.drag", null); + yesdrag(event.view, mousemoving); + noevent$2(event); + gestures.mouse("end", event); + } + + function touchstarted(event, d) { + if (!filter.call(this, event, d)) return; + var touches = event.changedTouches, + c = container.call(this, event, d), + n = touches.length, i, gesture; + + for (i = 0; i < n; ++i) { + if (gesture = beforestart(this, c, event, d, touches[i].identifier, touches[i])) { + nopropagation$2(event); + gesture("start", event, touches[i]); + } + } + } + + function touchmoved(event) { + var touches = event.changedTouches, + n = touches.length, i, gesture; + + for (i = 0; i < n; ++i) { + if (gesture = gestures[touches[i].identifier]) { + noevent$2(event); + gesture("drag", event, touches[i]); + } + } + } + + function touchended(event) { + var touches = event.changedTouches, + n = touches.length, i, gesture; + + if (touchending) clearTimeout(touchending); + touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed! + for (i = 0; i < n; ++i) { + if (gesture = gestures[touches[i].identifier]) { + nopropagation$2(event); + gesture("end", event, touches[i]); + } + } + } + + function beforestart(that, container, event, d, identifier, touch) { + var dispatch = listeners.copy(), + p = pointer(touch || event, container), dx, dy, + s; + + if ((s = subject.call(that, new DragEvent("beforestart", { + sourceEvent: event, + target: drag, + identifier, + active, + x: p[0], + y: p[1], + dx: 0, + dy: 0, + dispatch + }), d)) == null) return; + + dx = s.x - p[0] || 0; + dy = s.y - p[1] || 0; + + return function gesture(type, event, touch) { + var p0 = p, n; + switch (type) { + case "start": gestures[identifier] = gesture, n = active++; break; + case "end": delete gestures[identifier], --active; // falls through + case "drag": p = pointer(touch || event, container), n = active; break; + } + dispatch.call( + type, + that, + new DragEvent(type, { + sourceEvent: event, + subject: s, + target: drag, + identifier, + active: n, + x: p[0] + dx, + y: p[1] + dy, + dx: p[0] - p0[0], + dy: p[1] - p0[1], + dispatch + }), + d + ); + }; + } + + drag.filter = function(_) { + return arguments.length ? (filter = typeof _ === "function" ? _ : constant$9(!!_), drag) : filter; + }; + + drag.container = function(_) { + return arguments.length ? (container = typeof _ === "function" ? _ : constant$9(_), drag) : container; + }; + + drag.subject = function(_) { + return arguments.length ? (subject = typeof _ === "function" ? _ : constant$9(_), drag) : subject; + }; + + drag.touchable = function(_) { + return arguments.length ? (touchable = typeof _ === "function" ? _ : constant$9(!!_), drag) : touchable; + }; + + drag.on = function() { + var value = listeners.on.apply(listeners, arguments); + return value === listeners ? drag : value; + }; + + drag.clickDistance = function(_) { + return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2); + }; + + return drag; +} + +function define(constructor, factory, prototype) { + constructor.prototype = factory.prototype = prototype; + prototype.constructor = constructor; +} + +function extend(parent, definition) { + var prototype = Object.create(parent.prototype); + for (var key in definition) prototype[key] = definition[key]; + return prototype; +} + +function Color() {} + +var darker = 0.7; +var brighter = 1 / darker; + +var reI = "\\s*([+-]?\\d+)\\s*", + reN = "\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)\\s*", + reP = "\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)%\\s*", + reHex = /^#([0-9a-f]{3,8})$/, + reRgbInteger = new RegExp(`^rgb\\(${reI},${reI},${reI}\\)$`), + reRgbPercent = new RegExp(`^rgb\\(${reP},${reP},${reP}\\)$`), + reRgbaInteger = new RegExp(`^rgba\\(${reI},${reI},${reI},${reN}\\)$`), + reRgbaPercent = new RegExp(`^rgba\\(${reP},${reP},${reP},${reN}\\)$`), + reHslPercent = new RegExp(`^hsl\\(${reN},${reP},${reP}\\)$`), + reHslaPercent = new RegExp(`^hsla\\(${reN},${reP},${reP},${reN}\\)$`); + +var named = { + aliceblue: 0xf0f8ff, + antiquewhite: 0xfaebd7, + aqua: 0x00ffff, + aquamarine: 0x7fffd4, + azure: 0xf0ffff, + beige: 0xf5f5dc, + bisque: 0xffe4c4, + black: 0x000000, + blanchedalmond: 0xffebcd, + blue: 0x0000ff, + blueviolet: 0x8a2be2, + brown: 0xa52a2a, + burlywood: 0xdeb887, + cadetblue: 0x5f9ea0, + chartreuse: 0x7fff00, + chocolate: 0xd2691e, + coral: 0xff7f50, + cornflowerblue: 0x6495ed, + cornsilk: 0xfff8dc, + crimson: 0xdc143c, + cyan: 0x00ffff, + darkblue: 0x00008b, + darkcyan: 0x008b8b, + darkgoldenrod: 0xb8860b, + darkgray: 0xa9a9a9, + darkgreen: 0x006400, + darkgrey: 0xa9a9a9, + darkkhaki: 0xbdb76b, + darkmagenta: 0x8b008b, + darkolivegreen: 0x556b2f, + darkorange: 0xff8c00, + darkorchid: 0x9932cc, + darkred: 0x8b0000, + darksalmon: 0xe9967a, + darkseagreen: 0x8fbc8f, + darkslateblue: 0x483d8b, + darkslategray: 0x2f4f4f, + darkslategrey: 0x2f4f4f, + darkturquoise: 0x00ced1, + darkviolet: 0x9400d3, + deeppink: 0xff1493, + deepskyblue: 0x00bfff, + dimgray: 0x696969, + dimgrey: 0x696969, + dodgerblue: 0x1e90ff, + firebrick: 0xb22222, + floralwhite: 0xfffaf0, + forestgreen: 0x228b22, + fuchsia: 0xff00ff, + gainsboro: 0xdcdcdc, + ghostwhite: 0xf8f8ff, + gold: 0xffd700, + goldenrod: 0xdaa520, + gray: 0x808080, + green: 0x008000, + greenyellow: 0xadff2f, + grey: 0x808080, + honeydew: 0xf0fff0, + hotpink: 0xff69b4, + indianred: 0xcd5c5c, + indigo: 0x4b0082, + ivory: 0xfffff0, + khaki: 0xf0e68c, + lavender: 0xe6e6fa, + lavenderblush: 0xfff0f5, + lawngreen: 0x7cfc00, + lemonchiffon: 0xfffacd, + lightblue: 0xadd8e6, + lightcoral: 0xf08080, + lightcyan: 0xe0ffff, + lightgoldenrodyellow: 0xfafad2, + lightgray: 0xd3d3d3, + lightgreen: 0x90ee90, + lightgrey: 0xd3d3d3, + lightpink: 0xffb6c1, + lightsalmon: 0xffa07a, + lightseagreen: 0x20b2aa, + lightskyblue: 0x87cefa, + lightslategray: 0x778899, + lightslategrey: 0x778899, + lightsteelblue: 0xb0c4de, + lightyellow: 0xffffe0, + lime: 0x00ff00, + limegreen: 0x32cd32, + linen: 0xfaf0e6, + magenta: 0xff00ff, + maroon: 0x800000, + mediumaquamarine: 0x66cdaa, + mediumblue: 0x0000cd, + mediumorchid: 0xba55d3, + mediumpurple: 0x9370db, + mediumseagreen: 0x3cb371, + mediumslateblue: 0x7b68ee, + mediumspringgreen: 0x00fa9a, + mediumturquoise: 0x48d1cc, + mediumvioletred: 0xc71585, + midnightblue: 0x191970, + mintcream: 0xf5fffa, + mistyrose: 0xffe4e1, + moccasin: 0xffe4b5, + navajowhite: 0xffdead, + navy: 0x000080, + oldlace: 0xfdf5e6, + olive: 0x808000, + olivedrab: 0x6b8e23, + orange: 0xffa500, + orangered: 0xff4500, + orchid: 0xda70d6, + palegoldenrod: 0xeee8aa, + palegreen: 0x98fb98, + paleturquoise: 0xafeeee, + palevioletred: 0xdb7093, + papayawhip: 0xffefd5, + peachpuff: 0xffdab9, + peru: 0xcd853f, + pink: 0xffc0cb, + plum: 0xdda0dd, + powderblue: 0xb0e0e6, + purple: 0x800080, + rebeccapurple: 0x663399, + red: 0xff0000, + rosybrown: 0xbc8f8f, + royalblue: 0x4169e1, + saddlebrown: 0x8b4513, + salmon: 0xfa8072, + sandybrown: 0xf4a460, + seagreen: 0x2e8b57, + seashell: 0xfff5ee, + sienna: 0xa0522d, + silver: 0xc0c0c0, + skyblue: 0x87ceeb, + slateblue: 0x6a5acd, + slategray: 0x708090, + slategrey: 0x708090, + snow: 0xfffafa, + springgreen: 0x00ff7f, + steelblue: 0x4682b4, + tan: 0xd2b48c, + teal: 0x008080, + thistle: 0xd8bfd8, + tomato: 0xff6347, + turquoise: 0x40e0d0, + violet: 0xee82ee, + wheat: 0xf5deb3, + white: 0xffffff, + whitesmoke: 0xf5f5f5, + yellow: 0xffff00, + yellowgreen: 0x9acd32 +}; + +define(Color, color, { + copy(channels) { + return Object.assign(new this.constructor, this, channels); + }, + displayable() { + return this.rgb().displayable(); + }, + hex: color_formatHex, // Deprecated! Use color.formatHex. + formatHex: color_formatHex, + formatHex8: color_formatHex8, + formatHsl: color_formatHsl, + formatRgb: color_formatRgb, + toString: color_formatRgb +}); + +function color_formatHex() { + return this.rgb().formatHex(); +} + +function color_formatHex8() { + return this.rgb().formatHex8(); +} + +function color_formatHsl() { + return hslConvert(this).formatHsl(); +} + +function color_formatRgb() { + return this.rgb().formatRgb(); +} + +function color(format) { + var m, l; + format = (format + "").trim().toLowerCase(); + return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000 + : l === 3 ? new Rgb((m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) // #f00 + : l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000 + : l === 4 ? rgba((m >> 12 & 0xf) | (m >> 8 & 0xf0), (m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), (((m & 0xf) << 4) | (m & 0xf)) / 0xff) // #f000 + : null) // invalid hex + : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0) + : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%) + : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1) + : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1) + : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%) + : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1) + : named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins + : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0) + : null; +} + +function rgbn(n) { + return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1); +} + +function rgba(r, g, b, a) { + if (a <= 0) r = g = b = NaN; + return new Rgb(r, g, b, a); +} + +function rgbConvert(o) { + if (!(o instanceof Color)) o = color(o); + if (!o) return new Rgb; + o = o.rgb(); + return new Rgb(o.r, o.g, o.b, o.opacity); +} + +function rgb(r, g, b, opacity) { + return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity); +} + +function Rgb(r, g, b, opacity) { + this.r = +r; + this.g = +g; + this.b = +b; + this.opacity = +opacity; +} + +define(Rgb, rgb, extend(Color, { + brighter(k) { + k = k == null ? brighter : Math.pow(brighter, k); + return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); + }, + darker(k) { + k = k == null ? darker : Math.pow(darker, k); + return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); + }, + rgb() { + return this; + }, + clamp() { + return new Rgb(clampi(this.r), clampi(this.g), clampi(this.b), clampa(this.opacity)); + }, + displayable() { + return (-0.5 <= this.r && this.r < 255.5) + && (-0.5 <= this.g && this.g < 255.5) + && (-0.5 <= this.b && this.b < 255.5) + && (0 <= this.opacity && this.opacity <= 1); + }, + hex: rgb_formatHex, // Deprecated! Use color.formatHex. + formatHex: rgb_formatHex, + formatHex8: rgb_formatHex8, + formatRgb: rgb_formatRgb, + toString: rgb_formatRgb +})); + +function rgb_formatHex() { + return `#${hex(this.r)}${hex(this.g)}${hex(this.b)}`; +} + +function rgb_formatHex8() { + return `#${hex(this.r)}${hex(this.g)}${hex(this.b)}${hex((isNaN(this.opacity) ? 1 : this.opacity) * 255)}`; +} + +function rgb_formatRgb() { + const a = clampa(this.opacity); + return `${a === 1 ? "rgb(" : "rgba("}${clampi(this.r)}, ${clampi(this.g)}, ${clampi(this.b)}${a === 1 ? ")" : `, ${a})`}`; +} + +function clampa(opacity) { + return isNaN(opacity) ? 1 : Math.max(0, Math.min(1, opacity)); +} + +function clampi(value) { + return Math.max(0, Math.min(255, Math.round(value) || 0)); +} + +function hex(value) { + value = clampi(value); + return (value < 16 ? "0" : "") + value.toString(16); +} + +function hsla(h, s, l, a) { + if (a <= 0) h = s = l = NaN; + else if (l <= 0 || l >= 1) h = s = NaN; + else if (s <= 0) h = NaN; + return new Hsl(h, s, l, a); +} + +function hslConvert(o) { + if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity); + if (!(o instanceof Color)) o = color(o); + if (!o) return new Hsl; + if (o instanceof Hsl) return o; + o = o.rgb(); + var r = o.r / 255, + g = o.g / 255, + b = o.b / 255, + min = Math.min(r, g, b), + max = Math.max(r, g, b), + h = NaN, + s = max - min, + l = (max + min) / 2; + if (s) { + if (r === max) h = (g - b) / s + (g < b) * 6; + else if (g === max) h = (b - r) / s + 2; + else h = (r - g) / s + 4; + s /= l < 0.5 ? max + min : 2 - max - min; + h *= 60; + } else { + s = l > 0 && l < 1 ? 0 : h; + } + return new Hsl(h, s, l, o.opacity); +} + +function hsl$2(h, s, l, opacity) { + return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity); +} + +function Hsl(h, s, l, opacity) { + this.h = +h; + this.s = +s; + this.l = +l; + this.opacity = +opacity; +} + +define(Hsl, hsl$2, extend(Color, { + brighter(k) { + k = k == null ? brighter : Math.pow(brighter, k); + return new Hsl(this.h, this.s, this.l * k, this.opacity); + }, + darker(k) { + k = k == null ? darker : Math.pow(darker, k); + return new Hsl(this.h, this.s, this.l * k, this.opacity); + }, + rgb() { + var h = this.h % 360 + (this.h < 0) * 360, + s = isNaN(h) || isNaN(this.s) ? 0 : this.s, + l = this.l, + m2 = l + (l < 0.5 ? l : 1 - l) * s, + m1 = 2 * l - m2; + return new Rgb( + hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2), + hsl2rgb(h, m1, m2), + hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2), + this.opacity + ); + }, + clamp() { + return new Hsl(clamph(this.h), clampt(this.s), clampt(this.l), clampa(this.opacity)); + }, + displayable() { + return (0 <= this.s && this.s <= 1 || isNaN(this.s)) + && (0 <= this.l && this.l <= 1) + && (0 <= this.opacity && this.opacity <= 1); + }, + formatHsl() { + const a = clampa(this.opacity); + return `${a === 1 ? "hsl(" : "hsla("}${clamph(this.h)}, ${clampt(this.s) * 100}%, ${clampt(this.l) * 100}%${a === 1 ? ")" : `, ${a})`}`; + } +})); + +function clamph(value) { + value = (value || 0) % 360; + return value < 0 ? value + 360 : value; +} + +function clampt(value) { + return Math.max(0, Math.min(1, value || 0)); +} + +/* From FvD 13.37, CSS Color Module Level 3 */ +function hsl2rgb(h, m1, m2) { + return (h < 60 ? m1 + (m2 - m1) * h / 60 + : h < 180 ? m2 + : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60 + : m1) * 255; +} + +const radians$1 = Math.PI / 180; +const degrees$2 = 180 / Math.PI; + +// https://observablehq.com/@mbostock/lab-and-rgb +const K = 18, + Xn = 0.96422, + Yn = 1, + Zn = 0.82521, + t0$1 = 4 / 29, + t1$1 = 6 / 29, + t2 = 3 * t1$1 * t1$1, + t3 = t1$1 * t1$1 * t1$1; + +function labConvert(o) { + if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity); + if (o instanceof Hcl) return hcl2lab(o); + if (!(o instanceof Rgb)) o = rgbConvert(o); + var r = rgb2lrgb(o.r), + g = rgb2lrgb(o.g), + b = rgb2lrgb(o.b), + y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn), x, z; + if (r === g && g === b) x = z = y; else { + x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn); + z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn); + } + return new Lab(116 * y - 16, 500 * (x - y), 200 * (y - z), o.opacity); +} + +function gray(l, opacity) { + return new Lab(l, 0, 0, opacity == null ? 1 : opacity); +} + +function lab$1(l, a, b, opacity) { + return arguments.length === 1 ? labConvert(l) : new Lab(l, a, b, opacity == null ? 1 : opacity); +} + +function Lab(l, a, b, opacity) { + this.l = +l; + this.a = +a; + this.b = +b; + this.opacity = +opacity; +} + +define(Lab, lab$1, extend(Color, { + brighter(k) { + return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity); + }, + darker(k) { + return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity); + }, + rgb() { + var y = (this.l + 16) / 116, + x = isNaN(this.a) ? y : y + this.a / 500, + z = isNaN(this.b) ? y : y - this.b / 200; + x = Xn * lab2xyz(x); + y = Yn * lab2xyz(y); + z = Zn * lab2xyz(z); + return new Rgb( + lrgb2rgb( 3.1338561 * x - 1.6168667 * y - 0.4906146 * z), + lrgb2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z), + lrgb2rgb( 0.0719453 * x - 0.2289914 * y + 1.4052427 * z), + this.opacity + ); + } +})); + +function xyz2lab(t) { + return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0$1; +} + +function lab2xyz(t) { + return t > t1$1 ? t * t * t : t2 * (t - t0$1); +} + +function lrgb2rgb(x) { + return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055); +} + +function rgb2lrgb(x) { + return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); +} + +function hclConvert(o) { + if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity); + if (!(o instanceof Lab)) o = labConvert(o); + if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0 < o.l && o.l < 100 ? 0 : NaN, o.l, o.opacity); + var h = Math.atan2(o.b, o.a) * degrees$2; + return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity); +} + +function lch(l, c, h, opacity) { + return arguments.length === 1 ? hclConvert(l) : new Hcl(h, c, l, opacity == null ? 1 : opacity); +} + +function hcl$2(h, c, l, opacity) { + return arguments.length === 1 ? hclConvert(h) : new Hcl(h, c, l, opacity == null ? 1 : opacity); +} + +function Hcl(h, c, l, opacity) { + this.h = +h; + this.c = +c; + this.l = +l; + this.opacity = +opacity; +} + +function hcl2lab(o) { + if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity); + var h = o.h * radians$1; + return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity); +} + +define(Hcl, hcl$2, extend(Color, { + brighter(k) { + return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity); + }, + darker(k) { + return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity); + }, + rgb() { + return hcl2lab(this).rgb(); + } +})); + +var A = -0.14861, + B$1 = +1.78277, + C = -0.29227, + D$1 = -0.90649, + E = +1.97294, + ED = E * D$1, + EB = E * B$1, + BC_DA = B$1 * C - D$1 * A; + +function cubehelixConvert(o) { + if (o instanceof Cubehelix) return new Cubehelix(o.h, o.s, o.l, o.opacity); + if (!(o instanceof Rgb)) o = rgbConvert(o); + var r = o.r / 255, + g = o.g / 255, + b = o.b / 255, + l = (BC_DA * b + ED * r - EB * g) / (BC_DA + ED - EB), + bl = b - l, + k = (E * (g - l) - C * bl) / D$1, + s = Math.sqrt(k * k + bl * bl) / (E * l * (1 - l)), // NaN if l=0 or l=1 + h = s ? Math.atan2(k, bl) * degrees$2 - 120 : NaN; + return new Cubehelix(h < 0 ? h + 360 : h, s, l, o.opacity); +} + +function cubehelix$3(h, s, l, opacity) { + return arguments.length === 1 ? cubehelixConvert(h) : new Cubehelix(h, s, l, opacity == null ? 1 : opacity); +} + +function Cubehelix(h, s, l, opacity) { + this.h = +h; + this.s = +s; + this.l = +l; + this.opacity = +opacity; +} + +define(Cubehelix, cubehelix$3, extend(Color, { + brighter(k) { + k = k == null ? brighter : Math.pow(brighter, k); + return new Cubehelix(this.h, this.s, this.l * k, this.opacity); + }, + darker(k) { + k = k == null ? darker : Math.pow(darker, k); + return new Cubehelix(this.h, this.s, this.l * k, this.opacity); + }, + rgb() { + var h = isNaN(this.h) ? 0 : (this.h + 120) * radians$1, + l = +this.l, + a = isNaN(this.s) ? 0 : this.s * l * (1 - l), + cosh = Math.cos(h), + sinh = Math.sin(h); + return new Rgb( + 255 * (l + a * (A * cosh + B$1 * sinh)), + 255 * (l + a * (C * cosh + D$1 * sinh)), + 255 * (l + a * (E * cosh)), + this.opacity + ); + } +})); + +function basis$1(t1, v0, v1, v2, v3) { + var t2 = t1 * t1, t3 = t2 * t1; + return ((1 - 3 * t1 + 3 * t2 - t3) * v0 + + (4 - 6 * t2 + 3 * t3) * v1 + + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 + + t3 * v3) / 6; +} + +function basis$2(values) { + var n = values.length - 1; + return function(t) { + var i = t <= 0 ? (t = 0) : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n), + v1 = values[i], + v2 = values[i + 1], + v0 = i > 0 ? values[i - 1] : 2 * v1 - v2, + v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1; + return basis$1((t - i / n) * n, v0, v1, v2, v3); + }; +} + +function basisClosed$1(values) { + var n = values.length; + return function(t) { + var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n), + v0 = values[(i + n - 1) % n], + v1 = values[i % n], + v2 = values[(i + 1) % n], + v3 = values[(i + 2) % n]; + return basis$1((t - i / n) * n, v0, v1, v2, v3); + }; +} + +var constant$8 = x => () => x; + +function linear$2(a, d) { + return function(t) { + return a + t * d; + }; +} + +function exponential$1(a, b, y) { + return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) { + return Math.pow(a + t * b, y); + }; +} + +function hue$1(a, b) { + var d = b - a; + return d ? linear$2(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d) : constant$8(isNaN(a) ? b : a); +} + +function gamma$1(y) { + return (y = +y) === 1 ? nogamma : function(a, b) { + return b - a ? exponential$1(a, b, y) : constant$8(isNaN(a) ? b : a); + }; +} + +function nogamma(a, b) { + var d = b - a; + return d ? linear$2(a, d) : constant$8(isNaN(a) ? b : a); +} + +var interpolateRgb = (function rgbGamma(y) { + var color = gamma$1(y); + + function rgb$1(start, end) { + var r = color((start = rgb(start)).r, (end = rgb(end)).r), + g = color(start.g, end.g), + b = color(start.b, end.b), + opacity = nogamma(start.opacity, end.opacity); + return function(t) { + start.r = r(t); + start.g = g(t); + start.b = b(t); + start.opacity = opacity(t); + return start + ""; + }; + } + + rgb$1.gamma = rgbGamma; + + return rgb$1; +})(1); + +function rgbSpline(spline) { + return function(colors) { + var n = colors.length, + r = new Array(n), + g = new Array(n), + b = new Array(n), + i, color; + for (i = 0; i < n; ++i) { + color = rgb(colors[i]); + r[i] = color.r || 0; + g[i] = color.g || 0; + b[i] = color.b || 0; + } + r = spline(r); + g = spline(g); + b = spline(b); + color.opacity = 1; + return function(t) { + color.r = r(t); + color.g = g(t); + color.b = b(t); + return color + ""; + }; + }; +} + +var rgbBasis = rgbSpline(basis$2); +var rgbBasisClosed = rgbSpline(basisClosed$1); + +function numberArray(a, b) { + if (!b) b = []; + var n = a ? Math.min(b.length, a.length) : 0, + c = b.slice(), + i; + return function(t) { + for (i = 0; i < n; ++i) c[i] = a[i] * (1 - t) + b[i] * t; + return c; + }; +} + +function isNumberArray(x) { + return ArrayBuffer.isView(x) && !(x instanceof DataView); +} + +function array$3(a, b) { + return (isNumberArray(b) ? numberArray : genericArray)(a, b); +} + +function genericArray(a, b) { + var nb = b ? b.length : 0, + na = a ? Math.min(nb, a.length) : 0, + x = new Array(na), + c = new Array(nb), + i; + + for (i = 0; i < na; ++i) x[i] = interpolate$2(a[i], b[i]); + for (; i < nb; ++i) c[i] = b[i]; + + return function(t) { + for (i = 0; i < na; ++i) c[i] = x[i](t); + return c; + }; +} + +function date$1(a, b) { + var d = new Date; + return a = +a, b = +b, function(t) { + return d.setTime(a * (1 - t) + b * t), d; + }; +} + +function interpolateNumber(a, b) { + return a = +a, b = +b, function(t) { + return a * (1 - t) + b * t; + }; +} + +function object$1(a, b) { + var i = {}, + c = {}, + k; + + if (a === null || typeof a !== "object") a = {}; + if (b === null || typeof b !== "object") b = {}; + + for (k in b) { + if (k in a) { + i[k] = interpolate$2(a[k], b[k]); + } else { + c[k] = b[k]; + } + } + + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; +} + +var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, + reB = new RegExp(reA.source, "g"); + +function zero(b) { + return function() { + return b; + }; +} + +function one(b) { + return function(t) { + return b(t) + ""; + }; +} + +function interpolateString(a, b) { + var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b + am, // current match in a + bm, // current match in b + bs, // string preceding current number in b, if any + i = -1, // index in s + s = [], // string constants and placeholders + q = []; // number interpolators + + // Coerce inputs to strings. + a = a + "", b = b + ""; + + // Interpolate pairs of numbers in a & b. + while ((am = reA.exec(a)) + && (bm = reB.exec(b))) { + if ((bs = bm.index) > bi) { // a string precedes the next number in b + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; // coalesce with previous string + else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match + if (s[i]) s[i] += bm; // coalesce with previous string + else s[++i] = bm; + } else { // interpolate non-matching numbers + s[++i] = null; + q.push({i: i, x: interpolateNumber(am, bm)}); + } + bi = reB.lastIndex; + } + + // Add remains of b. + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; // coalesce with previous string + else s[++i] = bs; + } + + // Special optimization for only a single match. + // Otherwise, interpolate each of the numbers and rejoin the string. + return s.length < 2 ? (q[0] + ? one(q[0].x) + : zero(b)) + : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }); +} + +function interpolate$2(a, b) { + var t = typeof b, c; + return b == null || t === "boolean" ? constant$8(b) + : (t === "number" ? interpolateNumber + : t === "string" ? ((c = color(b)) ? (b = c, interpolateRgb) : interpolateString) + : b instanceof color ? interpolateRgb + : b instanceof Date ? date$1 + : isNumberArray(b) ? numberArray + : Array.isArray(b) ? genericArray + : typeof b.valueOf !== "function" && typeof b.toString !== "function" || isNaN(b) ? object$1 + : interpolateNumber)(a, b); +} + +function discrete(range) { + var n = range.length; + return function(t) { + return range[Math.max(0, Math.min(n - 1, Math.floor(t * n)))]; + }; +} + +function hue(a, b) { + var i = hue$1(+a, +b); + return function(t) { + var x = i(t); + return x - 360 * Math.floor(x / 360); + }; +} + +function interpolateRound(a, b) { + return a = +a, b = +b, function(t) { + return Math.round(a * (1 - t) + b * t); + }; +} + +var degrees$1 = 180 / Math.PI; + +var identity$7 = { + translateX: 0, + translateY: 0, + rotate: 0, + skewX: 0, + scaleX: 1, + scaleY: 1 +}; + +function decompose(a, b, c, d, e, f) { + var scaleX, scaleY, skewX; + if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX; + if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX; + if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY; + if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX; + return { + translateX: e, + translateY: f, + rotate: Math.atan2(b, a) * degrees$1, + skewX: Math.atan(skewX) * degrees$1, + scaleX: scaleX, + scaleY: scaleY + }; +} + +var svgNode; + +/* eslint-disable no-undef */ +function parseCss(value) { + const m = new (typeof DOMMatrix === "function" ? DOMMatrix : WebKitCSSMatrix)(value + ""); + return m.isIdentity ? identity$7 : decompose(m.a, m.b, m.c, m.d, m.e, m.f); +} + +function parseSvg(value) { + if (value == null) return identity$7; + if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g"); + svgNode.setAttribute("transform", value); + if (!(value = svgNode.transform.baseVal.consolidate())) return identity$7; + value = value.matrix; + return decompose(value.a, value.b, value.c, value.d, value.e, value.f); +} + +function interpolateTransform(parse, pxComma, pxParen, degParen) { + + function pop(s) { + return s.length ? s.pop() + " " : ""; + } + + function translate(xa, ya, xb, yb, s, q) { + if (xa !== xb || ya !== yb) { + var i = s.push("translate(", null, pxComma, null, pxParen); + q.push({i: i - 4, x: interpolateNumber(xa, xb)}, {i: i - 2, x: interpolateNumber(ya, yb)}); + } else if (xb || yb) { + s.push("translate(" + xb + pxComma + yb + pxParen); + } + } + + function rotate(a, b, s, q) { + if (a !== b) { + if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path + q.push({i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: interpolateNumber(a, b)}); + } else if (b) { + s.push(pop(s) + "rotate(" + b + degParen); + } + } + + function skewX(a, b, s, q) { + if (a !== b) { + q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: interpolateNumber(a, b)}); + } else if (b) { + s.push(pop(s) + "skewX(" + b + degParen); + } + } + + function scale(xa, ya, xb, yb, s, q) { + if (xa !== xb || ya !== yb) { + var i = s.push(pop(s) + "scale(", null, ",", null, ")"); + q.push({i: i - 4, x: interpolateNumber(xa, xb)}, {i: i - 2, x: interpolateNumber(ya, yb)}); + } else if (xb !== 1 || yb !== 1) { + s.push(pop(s) + "scale(" + xb + "," + yb + ")"); + } + } + + return function(a, b) { + var s = [], // string constants and placeholders + q = []; // number interpolators + a = parse(a), b = parse(b); + translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q); + rotate(a.rotate, b.rotate, s, q); + skewX(a.skewX, b.skewX, s, q); + scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q); + a = b = null; // gc + return function(t) { + var i = -1, n = q.length, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + }; +} + +var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)"); +var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")"); + +var epsilon2$1 = 1e-12; + +function cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; +} + +function sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; +} + +function tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); +} + +var interpolateZoom = (function zoomRho(rho, rho2, rho4) { + + // p0 = [ux0, uy0, w0] + // p1 = [ux1, uy1, w1] + function zoom(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], + ux1 = p1[0], uy1 = p1[1], w1 = p1[2], + dx = ux1 - ux0, + dy = uy1 - uy0, + d2 = dx * dx + dy * dy, + i, + S; + + // Special case for u0 ≅ u1. + if (d2 < epsilon2$1) { + S = Math.log(w1 / w0) / rho; + i = function(t) { + return [ + ux0 + t * dx, + uy0 + t * dy, + w0 * Math.exp(rho * t * S) + ]; + }; + } + + // General case. + else { + var d1 = Math.sqrt(d2), + b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1), + b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1), + r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), + r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1); + S = (r1 - r0) / rho; + i = function(t) { + var s = t * S, + coshr0 = cosh(r0), + u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0)); + return [ + ux0 + u * dx, + uy0 + u * dy, + w0 * coshr0 / cosh(rho * s + r0) + ]; + }; + } + + i.duration = S * 1000 * rho / Math.SQRT2; + + return i; + } + + zoom.rho = function(_) { + var _1 = Math.max(1e-3, +_), _2 = _1 * _1, _4 = _2 * _2; + return zoomRho(_1, _2, _4); + }; + + return zoom; +})(Math.SQRT2, 2, 4); + +function hsl(hue) { + return function(start, end) { + var h = hue((start = hsl$2(start)).h, (end = hsl$2(end)).h), + s = nogamma(start.s, end.s), + l = nogamma(start.l, end.l), + opacity = nogamma(start.opacity, end.opacity); + return function(t) { + start.h = h(t); + start.s = s(t); + start.l = l(t); + start.opacity = opacity(t); + return start + ""; + }; + } +} + +var hsl$1 = hsl(hue$1); +var hslLong = hsl(nogamma); + +function lab(start, end) { + var l = nogamma((start = lab$1(start)).l, (end = lab$1(end)).l), + a = nogamma(start.a, end.a), + b = nogamma(start.b, end.b), + opacity = nogamma(start.opacity, end.opacity); + return function(t) { + start.l = l(t); + start.a = a(t); + start.b = b(t); + start.opacity = opacity(t); + return start + ""; + }; +} + +function hcl(hue) { + return function(start, end) { + var h = hue((start = hcl$2(start)).h, (end = hcl$2(end)).h), + c = nogamma(start.c, end.c), + l = nogamma(start.l, end.l), + opacity = nogamma(start.opacity, end.opacity); + return function(t) { + start.h = h(t); + start.c = c(t); + start.l = l(t); + start.opacity = opacity(t); + return start + ""; + }; + } +} + +var hcl$1 = hcl(hue$1); +var hclLong = hcl(nogamma); + +function cubehelix$1(hue) { + return (function cubehelixGamma(y) { + y = +y; + + function cubehelix(start, end) { + var h = hue((start = cubehelix$3(start)).h, (end = cubehelix$3(end)).h), + s = nogamma(start.s, end.s), + l = nogamma(start.l, end.l), + opacity = nogamma(start.opacity, end.opacity); + return function(t) { + start.h = h(t); + start.s = s(t); + start.l = l(Math.pow(t, y)); + start.opacity = opacity(t); + return start + ""; + }; + } + + cubehelix.gamma = cubehelixGamma; + + return cubehelix; + })(1); +} + +var cubehelix$2 = cubehelix$1(hue$1); +var cubehelixLong = cubehelix$1(nogamma); + +function piecewise(interpolate, values) { + if (values === undefined) values = interpolate, interpolate = interpolate$2; + var i = 0, n = values.length - 1, v = values[0], I = new Array(n < 0 ? 0 : n); + while (i < n) I[i] = interpolate(v, v = values[++i]); + return function(t) { + var i = Math.max(0, Math.min(n - 1, Math.floor(t *= n))); + return I[i](t - i); + }; +} + +function quantize$1(interpolator, n) { + var samples = new Array(n); + for (var i = 0; i < n; ++i) samples[i] = interpolator(i / (n - 1)); + return samples; +} + +var frame = 0, // is an animation frame pending? + timeout$1 = 0, // is a timeout pending? + interval$1 = 0, // are any timers active? + pokeDelay = 1000, // how frequently we check for clock skew + taskHead, + taskTail, + clockLast = 0, + clockNow = 0, + clockSkew = 0, + clock = typeof performance === "object" && performance.now ? performance : Date, + setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); }; + +function now() { + return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew); +} + +function clearNow() { + clockNow = 0; +} + +function Timer() { + this._call = + this._time = + this._next = null; +} + +Timer.prototype = timer.prototype = { + constructor: Timer, + restart: function(callback, delay, time) { + if (typeof callback !== "function") throw new TypeError("callback is not a function"); + time = (time == null ? now() : +time) + (delay == null ? 0 : +delay); + if (!this._next && taskTail !== this) { + if (taskTail) taskTail._next = this; + else taskHead = this; + taskTail = this; + } + this._call = callback; + this._time = time; + sleep(); + }, + stop: function() { + if (this._call) { + this._call = null; + this._time = Infinity; + sleep(); + } + } +}; + +function timer(callback, delay, time) { + var t = new Timer; + t.restart(callback, delay, time); + return t; +} + +function timerFlush() { + now(); // Get the current time, if not already set. + ++frame; // Pretend we’ve set an alarm, if we haven’t already. + var t = taskHead, e; + while (t) { + if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e); + t = t._next; + } + --frame; +} + +function wake() { + clockNow = (clockLast = clock.now()) + clockSkew; + frame = timeout$1 = 0; + try { + timerFlush(); + } finally { + frame = 0; + nap(); + clockNow = 0; + } +} + +function poke() { + var now = clock.now(), delay = now - clockLast; + if (delay > pokeDelay) clockSkew -= delay, clockLast = now; +} + +function nap() { + var t0, t1 = taskHead, t2, time = Infinity; + while (t1) { + if (t1._call) { + if (time > t1._time) time = t1._time; + t0 = t1, t1 = t1._next; + } else { + t2 = t1._next, t1._next = null; + t1 = t0 ? t0._next = t2 : taskHead = t2; + } + } + taskTail = t0; + sleep(time); +} + +function sleep(time) { + if (frame) return; // Soonest alarm already set, or will be. + if (timeout$1) timeout$1 = clearTimeout(timeout$1); + var delay = time - clockNow; // Strictly less than if we recomputed clockNow. + if (delay > 24) { + if (time < Infinity) timeout$1 = setTimeout(wake, time - clock.now() - clockSkew); + if (interval$1) interval$1 = clearInterval(interval$1); + } else { + if (!interval$1) clockLast = clock.now(), interval$1 = setInterval(poke, pokeDelay); + frame = 1, setFrame(wake); + } +} + +function timeout(callback, delay, time) { + var t = new Timer; + delay = delay == null ? 0 : +delay; + t.restart(elapsed => { + t.stop(); + callback(elapsed + delay); + }, delay, time); + return t; +} + +function interval(callback, delay, time) { + var t = new Timer, total = delay; + if (delay == null) return t.restart(callback, delay, time), t; + t._restart = t.restart; + t.restart = function(callback, delay, time) { + delay = +delay, time = time == null ? now() : +time; + t._restart(function tick(elapsed) { + elapsed += total; + t._restart(tick, total += delay, time); + callback(elapsed); + }, delay, time); + }; + t.restart(callback, delay, time); + return t; +} + +var emptyOn = dispatch("start", "end", "cancel", "interrupt"); +var emptyTween = []; + +var CREATED = 0; +var SCHEDULED = 1; +var STARTING = 2; +var STARTED = 3; +var RUNNING = 4; +var ENDING = 5; +var ENDED = 6; + +function schedule(node, name, id, index, group, timing) { + var schedules = node.__transition; + if (!schedules) node.__transition = {}; + else if (id in schedules) return; + create(node, id, { + name: name, + index: index, // For context during callback. + group: group, // For context during callback. + on: emptyOn, + tween: emptyTween, + time: timing.time, + delay: timing.delay, + duration: timing.duration, + ease: timing.ease, + timer: null, + state: CREATED + }); +} + +function init(node, id) { + var schedule = get(node, id); + if (schedule.state > CREATED) throw new Error("too late; already scheduled"); + return schedule; +} + +function set(node, id) { + var schedule = get(node, id); + if (schedule.state > STARTED) throw new Error("too late; already running"); + return schedule; +} + +function get(node, id) { + var schedule = node.__transition; + if (!schedule || !(schedule = schedule[id])) throw new Error("transition not found"); + return schedule; +} + +function create(node, id, self) { + var schedules = node.__transition, + tween; + + // Initialize the self timer when the transition is created. + // Note the actual delay is not known until the first callback! + schedules[id] = self; + self.timer = timer(schedule, 0, self.time); + + function schedule(elapsed) { + self.state = SCHEDULED; + self.timer.restart(start, self.delay, self.time); + + // If the elapsed delay is less than our first sleep, start immediately. + if (self.delay <= elapsed) start(elapsed - self.delay); + } + + function start(elapsed) { + var i, j, n, o; + + // If the state is not SCHEDULED, then we previously errored on start. + if (self.state !== SCHEDULED) return stop(); + + for (i in schedules) { + o = schedules[i]; + if (o.name !== self.name) continue; + + // While this element already has a starting transition during this frame, + // defer starting an interrupting transition until that transition has a + // chance to tick (and possibly end); see d3/d3-transition#54! + if (o.state === STARTED) return timeout(start); + + // Interrupt the active transition, if any. + if (o.state === RUNNING) { + o.state = ENDED; + o.timer.stop(); + o.on.call("interrupt", node, node.__data__, o.index, o.group); + delete schedules[i]; + } + + // Cancel any pre-empted transitions. + else if (+i < id) { + o.state = ENDED; + o.timer.stop(); + o.on.call("cancel", node, node.__data__, o.index, o.group); + delete schedules[i]; + } + } + + // Defer the first tick to end of the current frame; see d3/d3#1576. + // Note the transition may be canceled after start and before the first tick! + // Note this must be scheduled before the start event; see d3/d3-transition#16! + // Assuming this is successful, subsequent callbacks go straight to tick. + timeout(function() { + if (self.state === STARTED) { + self.state = RUNNING; + self.timer.restart(tick, self.delay, self.time); + tick(elapsed); + } + }); + + // Dispatch the start event. + // Note this must be done before the tween are initialized. + self.state = STARTING; + self.on.call("start", node, node.__data__, self.index, self.group); + if (self.state !== STARTING) return; // interrupted + self.state = STARTED; + + // Initialize the tween, deleting null tween. + tween = new Array(n = self.tween.length); + for (i = 0, j = -1; i < n; ++i) { + if (o = self.tween[i].value.call(node, node.__data__, self.index, self.group)) { + tween[++j] = o; + } + } + tween.length = j + 1; + } + + function tick(elapsed) { + var t = elapsed < self.duration ? self.ease.call(null, elapsed / self.duration) : (self.timer.restart(stop), self.state = ENDING, 1), + i = -1, + n = tween.length; + + while (++i < n) { + tween[i].call(node, t); + } + + // Dispatch the end event. + if (self.state === ENDING) { + self.on.call("end", node, node.__data__, self.index, self.group); + stop(); + } + } + + function stop() { + self.state = ENDED; + self.timer.stop(); + delete schedules[id]; + for (var i in schedules) return; // eslint-disable-line no-unused-vars + delete node.__transition; + } +} + +function interrupt(node, name) { + var schedules = node.__transition, + schedule, + active, + empty = true, + i; + + if (!schedules) return; + + name = name == null ? null : name + ""; + + for (i in schedules) { + if ((schedule = schedules[i]).name !== name) { empty = false; continue; } + active = schedule.state > STARTING && schedule.state < ENDING; + schedule.state = ENDED; + schedule.timer.stop(); + schedule.on.call(active ? "interrupt" : "cancel", node, node.__data__, schedule.index, schedule.group); + delete schedules[i]; + } + + if (empty) delete node.__transition; +} + +function selection_interrupt(name) { + return this.each(function() { + interrupt(this, name); + }); +} + +function tweenRemove(id, name) { + var tween0, tween1; + return function() { + var schedule = set(this, id), + tween = schedule.tween; + + // If this node shared tween with the previous node, + // just assign the updated shared tween and we’re done! + // Otherwise, copy-on-write. + if (tween !== tween0) { + tween1 = tween0 = tween; + for (var i = 0, n = tween1.length; i < n; ++i) { + if (tween1[i].name === name) { + tween1 = tween1.slice(); + tween1.splice(i, 1); + break; + } + } + } + + schedule.tween = tween1; + }; +} + +function tweenFunction(id, name, value) { + var tween0, tween1; + if (typeof value !== "function") throw new Error; + return function() { + var schedule = set(this, id), + tween = schedule.tween; + + // If this node shared tween with the previous node, + // just assign the updated shared tween and we’re done! + // Otherwise, copy-on-write. + if (tween !== tween0) { + tween1 = (tween0 = tween).slice(); + for (var t = {name: name, value: value}, i = 0, n = tween1.length; i < n; ++i) { + if (tween1[i].name === name) { + tween1[i] = t; + break; + } + } + if (i === n) tween1.push(t); + } + + schedule.tween = tween1; + }; +} + +function transition_tween(name, value) { + var id = this._id; + + name += ""; + + if (arguments.length < 2) { + var tween = get(this.node(), id).tween; + for (var i = 0, n = tween.length, t; i < n; ++i) { + if ((t = tween[i]).name === name) { + return t.value; + } + } + return null; + } + + return this.each((value == null ? tweenRemove : tweenFunction)(id, name, value)); +} + +function tweenValue(transition, name, value) { + var id = transition._id; + + transition.each(function() { + var schedule = set(this, id); + (schedule.value || (schedule.value = {}))[name] = value.apply(this, arguments); + }); + + return function(node) { + return get(node, id).value[name]; + }; +} + +function interpolate$1(a, b) { + var c; + return (typeof b === "number" ? interpolateNumber + : b instanceof color ? interpolateRgb + : (c = color(b)) ? (b = c, interpolateRgb) + : interpolateString)(a, b); +} + +function attrRemove(name) { + return function() { + this.removeAttribute(name); + }; +} + +function attrRemoveNS(fullname) { + return function() { + this.removeAttributeNS(fullname.space, fullname.local); + }; +} + +function attrConstant(name, interpolate, value1) { + var string00, + string1 = value1 + "", + interpolate0; + return function() { + var string0 = this.getAttribute(name); + return string0 === string1 ? null + : string0 === string00 ? interpolate0 + : interpolate0 = interpolate(string00 = string0, value1); + }; +} + +function attrConstantNS(fullname, interpolate, value1) { + var string00, + string1 = value1 + "", + interpolate0; + return function() { + var string0 = this.getAttributeNS(fullname.space, fullname.local); + return string0 === string1 ? null + : string0 === string00 ? interpolate0 + : interpolate0 = interpolate(string00 = string0, value1); + }; +} + +function attrFunction(name, interpolate, value) { + var string00, + string10, + interpolate0; + return function() { + var string0, value1 = value(this), string1; + if (value1 == null) return void this.removeAttribute(name); + string0 = this.getAttribute(name); + string1 = value1 + ""; + return string0 === string1 ? null + : string0 === string00 && string1 === string10 ? interpolate0 + : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); + }; +} + +function attrFunctionNS(fullname, interpolate, value) { + var string00, + string10, + interpolate0; + return function() { + var string0, value1 = value(this), string1; + if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local); + string0 = this.getAttributeNS(fullname.space, fullname.local); + string1 = value1 + ""; + return string0 === string1 ? null + : string0 === string00 && string1 === string10 ? interpolate0 + : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); + }; +} + +function transition_attr(name, value) { + var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate$1; + return this.attrTween(name, typeof value === "function" + ? (fullname.local ? attrFunctionNS : attrFunction)(fullname, i, tweenValue(this, "attr." + name, value)) + : value == null ? (fullname.local ? attrRemoveNS : attrRemove)(fullname) + : (fullname.local ? attrConstantNS : attrConstant)(fullname, i, value)); +} + +function attrInterpolate(name, i) { + return function(t) { + this.setAttribute(name, i.call(this, t)); + }; +} + +function attrInterpolateNS(fullname, i) { + return function(t) { + this.setAttributeNS(fullname.space, fullname.local, i.call(this, t)); + }; +} + +function attrTweenNS(fullname, value) { + var t0, i0; + function tween() { + var i = value.apply(this, arguments); + if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i); + return t0; + } + tween._value = value; + return tween; +} + +function attrTween(name, value) { + var t0, i0; + function tween() { + var i = value.apply(this, arguments); + if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i); + return t0; + } + tween._value = value; + return tween; +} + +function transition_attrTween(name, value) { + var key = "attr." + name; + if (arguments.length < 2) return (key = this.tween(key)) && key._value; + if (value == null) return this.tween(key, null); + if (typeof value !== "function") throw new Error; + var fullname = namespace(name); + return this.tween(key, (fullname.local ? attrTweenNS : attrTween)(fullname, value)); +} + +function delayFunction(id, value) { + return function() { + init(this, id).delay = +value.apply(this, arguments); + }; +} + +function delayConstant(id, value) { + return value = +value, function() { + init(this, id).delay = value; + }; +} + +function transition_delay(value) { + var id = this._id; + + return arguments.length + ? this.each((typeof value === "function" + ? delayFunction + : delayConstant)(id, value)) + : get(this.node(), id).delay; +} + +function durationFunction(id, value) { + return function() { + set(this, id).duration = +value.apply(this, arguments); + }; +} + +function durationConstant(id, value) { + return value = +value, function() { + set(this, id).duration = value; + }; +} + +function transition_duration(value) { + var id = this._id; + + return arguments.length + ? this.each((typeof value === "function" + ? durationFunction + : durationConstant)(id, value)) + : get(this.node(), id).duration; +} + +function easeConstant(id, value) { + if (typeof value !== "function") throw new Error; + return function() { + set(this, id).ease = value; + }; +} + +function transition_ease(value) { + var id = this._id; + + return arguments.length + ? this.each(easeConstant(id, value)) + : get(this.node(), id).ease; +} + +function easeVarying(id, value) { + return function() { + var v = value.apply(this, arguments); + if (typeof v !== "function") throw new Error; + set(this, id).ease = v; + }; +} + +function transition_easeVarying(value) { + if (typeof value !== "function") throw new Error; + return this.each(easeVarying(this._id, value)); +} + +function transition_filter(match) { + if (typeof match !== "function") match = matcher(match); + + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { + if ((node = group[i]) && match.call(node, node.__data__, i, group)) { + subgroup.push(node); + } + } + } + + return new Transition(subgroups, this._parents, this._name, this._id); +} + +function transition_merge(transition) { + if (transition._id !== this._id) throw new Error; + + for (var groups0 = this._groups, groups1 = transition._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { + for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { + if (node = group0[i] || group1[i]) { + merge[i] = node; + } + } + } + + for (; j < m0; ++j) { + merges[j] = groups0[j]; + } + + return new Transition(merges, this._parents, this._name, this._id); +} + +function start(name) { + return (name + "").trim().split(/^|\s+/).every(function(t) { + var i = t.indexOf("."); + if (i >= 0) t = t.slice(0, i); + return !t || t === "start"; + }); +} + +function onFunction(id, name, listener) { + var on0, on1, sit = start(name) ? init : set; + return function() { + var schedule = sit(this, id), + on = schedule.on; + + // If this node shared a dispatch with the previous node, + // just assign the updated shared dispatch and we’re done! + // Otherwise, copy-on-write. + if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener); + + schedule.on = on1; + }; +} + +function transition_on(name, listener) { + var id = this._id; + + return arguments.length < 2 + ? get(this.node(), id).on.on(name) + : this.each(onFunction(id, name, listener)); +} + +function removeFunction(id) { + return function() { + var parent = this.parentNode; + for (var i in this.__transition) if (+i !== id) return; + if (parent) parent.removeChild(this); + }; +} + +function transition_remove() { + return this.on("end.remove", removeFunction(this._id)); +} + +function transition_select(select) { + var name = this._name, + id = this._id; + + if (typeof select !== "function") select = selector(select); + + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { + if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + subgroup[i] = subnode; + schedule(subgroup[i], name, id, i, subgroup, get(node, id)); + } + } + } + + return new Transition(subgroups, this._parents, name, id); +} + +function transition_selectAll(select) { + var name = this._name, + id = this._id; + + if (typeof select !== "function") select = selectorAll(select); + + for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + for (var children = select.call(node, node.__data__, i, group), child, inherit = get(node, id), k = 0, l = children.length; k < l; ++k) { + if (child = children[k]) { + schedule(child, name, id, k, children, inherit); + } + } + subgroups.push(children); + parents.push(node); + } + } + } + + return new Transition(subgroups, parents, name, id); +} + +var Selection = selection.prototype.constructor; + +function transition_selection() { + return new Selection(this._groups, this._parents); +} + +function styleNull(name, interpolate) { + var string00, + string10, + interpolate0; + return function() { + var string0 = styleValue(this, name), + string1 = (this.style.removeProperty(name), styleValue(this, name)); + return string0 === string1 ? null + : string0 === string00 && string1 === string10 ? interpolate0 + : interpolate0 = interpolate(string00 = string0, string10 = string1); + }; +} + +function styleRemove(name) { + return function() { + this.style.removeProperty(name); + }; +} + +function styleConstant(name, interpolate, value1) { + var string00, + string1 = value1 + "", + interpolate0; + return function() { + var string0 = styleValue(this, name); + return string0 === string1 ? null + : string0 === string00 ? interpolate0 + : interpolate0 = interpolate(string00 = string0, value1); + }; +} + +function styleFunction(name, interpolate, value) { + var string00, + string10, + interpolate0; + return function() { + var string0 = styleValue(this, name), + value1 = value(this), + string1 = value1 + ""; + if (value1 == null) string1 = value1 = (this.style.removeProperty(name), styleValue(this, name)); + return string0 === string1 ? null + : string0 === string00 && string1 === string10 ? interpolate0 + : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); + }; +} + +function styleMaybeRemove(id, name) { + var on0, on1, listener0, key = "style." + name, event = "end." + key, remove; + return function() { + var schedule = set(this, id), + on = schedule.on, + listener = schedule.value[key] == null ? remove || (remove = styleRemove(name)) : undefined; + + // If this node shared a dispatch with the previous node, + // just assign the updated shared dispatch and we’re done! + // Otherwise, copy-on-write. + if (on !== on0 || listener0 !== listener) (on1 = (on0 = on).copy()).on(event, listener0 = listener); + + schedule.on = on1; + }; +} + +function transition_style(name, value, priority) { + var i = (name += "") === "transform" ? interpolateTransformCss : interpolate$1; + return value == null ? this + .styleTween(name, styleNull(name, i)) + .on("end.style." + name, styleRemove(name)) + : typeof value === "function" ? this + .styleTween(name, styleFunction(name, i, tweenValue(this, "style." + name, value))) + .each(styleMaybeRemove(this._id, name)) + : this + .styleTween(name, styleConstant(name, i, value), priority) + .on("end.style." + name, null); +} + +function styleInterpolate(name, i, priority) { + return function(t) { + this.style.setProperty(name, i.call(this, t), priority); + }; +} + +function styleTween(name, value, priority) { + var t, i0; + function tween() { + var i = value.apply(this, arguments); + if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority); + return t; + } + tween._value = value; + return tween; +} + +function transition_styleTween(name, value, priority) { + var key = "style." + (name += ""); + if (arguments.length < 2) return (key = this.tween(key)) && key._value; + if (value == null) return this.tween(key, null); + if (typeof value !== "function") throw new Error; + return this.tween(key, styleTween(name, value, priority == null ? "" : priority)); +} + +function textConstant(value) { + return function() { + this.textContent = value; + }; +} + +function textFunction(value) { + return function() { + var value1 = value(this); + this.textContent = value1 == null ? "" : value1; + }; +} + +function transition_text(value) { + return this.tween("text", typeof value === "function" + ? textFunction(tweenValue(this, "text", value)) + : textConstant(value == null ? "" : value + "")); +} + +function textInterpolate(i) { + return function(t) { + this.textContent = i.call(this, t); + }; +} + +function textTween(value) { + var t0, i0; + function tween() { + var i = value.apply(this, arguments); + if (i !== i0) t0 = (i0 = i) && textInterpolate(i); + return t0; + } + tween._value = value; + return tween; +} + +function transition_textTween(value) { + var key = "text"; + if (arguments.length < 1) return (key = this.tween(key)) && key._value; + if (value == null) return this.tween(key, null); + if (typeof value !== "function") throw new Error; + return this.tween(key, textTween(value)); +} + +function transition_transition() { + var name = this._name, + id0 = this._id, + id1 = newId(); + + for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + var inherit = get(node, id0); + schedule(node, name, id1, i, group, { + time: inherit.time + inherit.delay + inherit.duration, + delay: 0, + duration: inherit.duration, + ease: inherit.ease + }); + } + } + } + + return new Transition(groups, this._parents, name, id1); +} + +function transition_end() { + var on0, on1, that = this, id = that._id, size = that.size(); + return new Promise(function(resolve, reject) { + var cancel = {value: reject}, + end = {value: function() { if (--size === 0) resolve(); }}; + + that.each(function() { + var schedule = set(this, id), + on = schedule.on; + + // If this node shared a dispatch with the previous node, + // just assign the updated shared dispatch and we’re done! + // Otherwise, copy-on-write. + if (on !== on0) { + on1 = (on0 = on).copy(); + on1._.cancel.push(cancel); + on1._.interrupt.push(cancel); + on1._.end.push(end); + } + + schedule.on = on1; + }); + + // The selection was empty, resolve end immediately + if (size === 0) resolve(); + }); +} + +var id = 0; + +function Transition(groups, parents, name, id) { + this._groups = groups; + this._parents = parents; + this._name = name; + this._id = id; +} + +function transition(name) { + return selection().transition(name); +} + +function newId() { + return ++id; +} + +var selection_prototype = selection.prototype; + +Transition.prototype = transition.prototype = { + constructor: Transition, + select: transition_select, + selectAll: transition_selectAll, + selectChild: selection_prototype.selectChild, + selectChildren: selection_prototype.selectChildren, + filter: transition_filter, + merge: transition_merge, + selection: transition_selection, + transition: transition_transition, + call: selection_prototype.call, + nodes: selection_prototype.nodes, + node: selection_prototype.node, + size: selection_prototype.size, + empty: selection_prototype.empty, + each: selection_prototype.each, + on: transition_on, + attr: transition_attr, + attrTween: transition_attrTween, + style: transition_style, + styleTween: transition_styleTween, + text: transition_text, + textTween: transition_textTween, + remove: transition_remove, + tween: transition_tween, + delay: transition_delay, + duration: transition_duration, + ease: transition_ease, + easeVarying: transition_easeVarying, + end: transition_end, + [Symbol.iterator]: selection_prototype[Symbol.iterator] +}; + +const linear$1 = t => +t; + +function quadIn(t) { + return t * t; +} + +function quadOut(t) { + return t * (2 - t); +} + +function quadInOut(t) { + return ((t *= 2) <= 1 ? t * t : --t * (2 - t) + 1) / 2; +} + +function cubicIn(t) { + return t * t * t; +} + +function cubicOut(t) { + return --t * t * t + 1; +} + +function cubicInOut(t) { + return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; +} + +var exponent$1 = 3; + +var polyIn = (function custom(e) { + e = +e; + + function polyIn(t) { + return Math.pow(t, e); + } + + polyIn.exponent = custom; + + return polyIn; +})(exponent$1); + +var polyOut = (function custom(e) { + e = +e; + + function polyOut(t) { + return 1 - Math.pow(1 - t, e); + } + + polyOut.exponent = custom; + + return polyOut; +})(exponent$1); + +var polyInOut = (function custom(e) { + e = +e; + + function polyInOut(t) { + return ((t *= 2) <= 1 ? Math.pow(t, e) : 2 - Math.pow(2 - t, e)) / 2; + } + + polyInOut.exponent = custom; + + return polyInOut; +})(exponent$1); + +var pi$4 = Math.PI, + halfPi$3 = pi$4 / 2; + +function sinIn(t) { + return (+t === 1) ? 1 : 1 - Math.cos(t * halfPi$3); +} + +function sinOut(t) { + return Math.sin(t * halfPi$3); +} + +function sinInOut(t) { + return (1 - Math.cos(pi$4 * t)) / 2; +} + +// tpmt is two power minus ten times t scaled to [0,1] +function tpmt(x) { + return (Math.pow(2, -10 * x) - 0.0009765625) * 1.0009775171065494; +} + +function expIn(t) { + return tpmt(1 - +t); +} + +function expOut(t) { + return 1 - tpmt(t); +} + +function expInOut(t) { + return ((t *= 2) <= 1 ? tpmt(1 - t) : 2 - tpmt(t - 1)) / 2; +} + +function circleIn(t) { + return 1 - Math.sqrt(1 - t * t); +} + +function circleOut(t) { + return Math.sqrt(1 - --t * t); +} + +function circleInOut(t) { + return ((t *= 2) <= 1 ? 1 - Math.sqrt(1 - t * t) : Math.sqrt(1 - (t -= 2) * t) + 1) / 2; +} + +var b1 = 4 / 11, + b2 = 6 / 11, + b3 = 8 / 11, + b4 = 3 / 4, + b5 = 9 / 11, + b6 = 10 / 11, + b7 = 15 / 16, + b8 = 21 / 22, + b9 = 63 / 64, + b0 = 1 / b1 / b1; + +function bounceIn(t) { + return 1 - bounceOut(1 - t); +} + +function bounceOut(t) { + return (t = +t) < b1 ? b0 * t * t : t < b3 ? b0 * (t -= b2) * t + b4 : t < b6 ? b0 * (t -= b5) * t + b7 : b0 * (t -= b8) * t + b9; +} + +function bounceInOut(t) { + return ((t *= 2) <= 1 ? 1 - bounceOut(1 - t) : bounceOut(t - 1) + 1) / 2; +} + +var overshoot = 1.70158; + +var backIn = (function custom(s) { + s = +s; + + function backIn(t) { + return (t = +t) * t * (s * (t - 1) + t); + } + + backIn.overshoot = custom; + + return backIn; +})(overshoot); + +var backOut = (function custom(s) { + s = +s; + + function backOut(t) { + return --t * t * ((t + 1) * s + t) + 1; + } + + backOut.overshoot = custom; + + return backOut; +})(overshoot); + +var backInOut = (function custom(s) { + s = +s; + + function backInOut(t) { + return ((t *= 2) < 1 ? t * t * ((s + 1) * t - s) : (t -= 2) * t * ((s + 1) * t + s) + 2) / 2; + } + + backInOut.overshoot = custom; + + return backInOut; +})(overshoot); + +var tau$5 = 2 * Math.PI, + amplitude = 1, + period = 0.3; + +var elasticIn = (function custom(a, p) { + var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= tau$5); + + function elasticIn(t) { + return a * tpmt(-(--t)) * Math.sin((s - t) / p); + } + + elasticIn.amplitude = function(a) { return custom(a, p * tau$5); }; + elasticIn.period = function(p) { return custom(a, p); }; + + return elasticIn; +})(amplitude, period); + +var elasticOut = (function custom(a, p) { + var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= tau$5); + + function elasticOut(t) { + return 1 - a * tpmt(t = +t) * Math.sin((t + s) / p); + } + + elasticOut.amplitude = function(a) { return custom(a, p * tau$5); }; + elasticOut.period = function(p) { return custom(a, p); }; + + return elasticOut; +})(amplitude, period); + +var elasticInOut = (function custom(a, p) { + var s = Math.asin(1 / (a = Math.max(1, a))) * (p /= tau$5); + + function elasticInOut(t) { + return ((t = t * 2 - 1) < 0 + ? a * tpmt(-t) * Math.sin((s - t) / p) + : 2 - a * tpmt(t) * Math.sin((s + t) / p)) / 2; + } + + elasticInOut.amplitude = function(a) { return custom(a, p * tau$5); }; + elasticInOut.period = function(p) { return custom(a, p); }; + + return elasticInOut; +})(amplitude, period); + +var defaultTiming = { + time: null, // Set on use. + delay: 0, + duration: 250, + ease: cubicInOut +}; + +function inherit(node, id) { + var timing; + while (!(timing = node.__transition) || !(timing = timing[id])) { + if (!(node = node.parentNode)) { + throw new Error(`transition ${id} not found`); + } + } + return timing; +} + +function selection_transition(name) { + var id, + timing; + + if (name instanceof Transition) { + id = name._id, name = name._name; + } else { + id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + ""; + } + + for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + schedule(node, name, id, i, group, timing || inherit(node, id)); + } + } + } + + return new Transition(groups, this._parents, name, id); +} + +selection.prototype.interrupt = selection_interrupt; +selection.prototype.transition = selection_transition; + +var root = [null]; + +function active(node, name) { + var schedules = node.__transition, + schedule, + i; + + if (schedules) { + name = name == null ? null : name + ""; + for (i in schedules) { + if ((schedule = schedules[i]).state > SCHEDULED && schedule.name === name) { + return new Transition([[node]], root, name, +i); + } + } + } + + return null; +} + +var constant$7 = x => () => x; + +function BrushEvent(type, { + sourceEvent, + target, + selection, + mode, + dispatch +}) { + Object.defineProperties(this, { + type: {value: type, enumerable: true, configurable: true}, + sourceEvent: {value: sourceEvent, enumerable: true, configurable: true}, + target: {value: target, enumerable: true, configurable: true}, + selection: {value: selection, enumerable: true, configurable: true}, + mode: {value: mode, enumerable: true, configurable: true}, + _: {value: dispatch} + }); +} + +function nopropagation$1(event) { + event.stopImmediatePropagation(); +} + +function noevent$1(event) { + event.preventDefault(); + event.stopImmediatePropagation(); +} + +var MODE_DRAG = {name: "drag"}, + MODE_SPACE = {name: "space"}, + MODE_HANDLE = {name: "handle"}, + MODE_CENTER = {name: "center"}; + +const {abs: abs$3, max: max$2, min: min$1} = Math; + +function number1(e) { + return [+e[0], +e[1]]; +} + +function number2(e) { + return [number1(e[0]), number1(e[1])]; +} + +var X = { + name: "x", + handles: ["w", "e"].map(type), + input: function(x, e) { return x == null ? null : [[+x[0], e[0][1]], [+x[1], e[1][1]]]; }, + output: function(xy) { return xy && [xy[0][0], xy[1][0]]; } +}; + +var Y = { + name: "y", + handles: ["n", "s"].map(type), + input: function(y, e) { return y == null ? null : [[e[0][0], +y[0]], [e[1][0], +y[1]]]; }, + output: function(xy) { return xy && [xy[0][1], xy[1][1]]; } +}; + +var XY = { + name: "xy", + handles: ["n", "w", "e", "s", "nw", "ne", "sw", "se"].map(type), + input: function(xy) { return xy == null ? null : number2(xy); }, + output: function(xy) { return xy; } +}; + +var cursors = { + overlay: "crosshair", + selection: "move", + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" +}; + +var flipX = { + e: "w", + w: "e", + nw: "ne", + ne: "nw", + se: "sw", + sw: "se" +}; + +var flipY = { + n: "s", + s: "n", + nw: "sw", + ne: "se", + se: "ne", + sw: "nw" +}; + +var signsX = { + overlay: +1, + selection: +1, + n: null, + e: +1, + s: null, + w: -1, + nw: -1, + ne: +1, + se: +1, + sw: -1 +}; + +var signsY = { + overlay: +1, + selection: +1, + n: -1, + e: null, + s: +1, + w: null, + nw: -1, + ne: -1, + se: +1, + sw: +1 +}; + +function type(t) { + return {type: t}; +} + +// Ignore right-click, since that should open the context menu. +function defaultFilter$1(event) { + return !event.ctrlKey && !event.button; +} + +function defaultExtent$1() { + var svg = this.ownerSVGElement || this; + if (svg.hasAttribute("viewBox")) { + svg = svg.viewBox.baseVal; + return [[svg.x, svg.y], [svg.x + svg.width, svg.y + svg.height]]; + } + return [[0, 0], [svg.width.baseVal.value, svg.height.baseVal.value]]; +} + +function defaultTouchable$1() { + return navigator.maxTouchPoints || ("ontouchstart" in this); +} + +// Like d3.local, but with the name “__brush” rather than auto-generated. +function local(node) { + while (!node.__brush) if (!(node = node.parentNode)) return; + return node.__brush; +} + +function empty(extent) { + return extent[0][0] === extent[1][0] + || extent[0][1] === extent[1][1]; +} + +function brushSelection(node) { + var state = node.__brush; + return state ? state.dim.output(state.selection) : null; +} + +function brushX() { + return brush$1(X); +} + +function brushY() { + return brush$1(Y); +} + +function brush() { + return brush$1(XY); +} + +function brush$1(dim) { + var extent = defaultExtent$1, + filter = defaultFilter$1, + touchable = defaultTouchable$1, + keys = true, + listeners = dispatch("start", "brush", "end"), + handleSize = 6, + touchending; + + function brush(group) { + var overlay = group + .property("__brush", initialize) + .selectAll(".overlay") + .data([type("overlay")]); + + overlay.enter().append("rect") + .attr("class", "overlay") + .attr("pointer-events", "all") + .attr("cursor", cursors.overlay) + .merge(overlay) + .each(function() { + var extent = local(this).extent; + select(this) + .attr("x", extent[0][0]) + .attr("y", extent[0][1]) + .attr("width", extent[1][0] - extent[0][0]) + .attr("height", extent[1][1] - extent[0][1]); + }); + + group.selectAll(".selection") + .data([type("selection")]) + .enter().append("rect") + .attr("class", "selection") + .attr("cursor", cursors.selection) + .attr("fill", "#777") + .attr("fill-opacity", 0.3) + .attr("stroke", "#fff") + .attr("shape-rendering", "crispEdges"); + + var handle = group.selectAll(".handle") + .data(dim.handles, function(d) { return d.type; }); + + handle.exit().remove(); + + handle.enter().append("rect") + .attr("class", function(d) { return "handle handle--" + d.type; }) + .attr("cursor", function(d) { return cursors[d.type]; }); + + group + .each(redraw) + .attr("fill", "none") + .attr("pointer-events", "all") + .on("mousedown.brush", started) + .filter(touchable) + .on("touchstart.brush", started) + .on("touchmove.brush", touchmoved) + .on("touchend.brush touchcancel.brush", touchended) + .style("touch-action", "none") + .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)"); + } + + brush.move = function(group, selection, event) { + if (group.tween) { + group + .on("start.brush", function(event) { emitter(this, arguments).beforestart().start(event); }) + .on("interrupt.brush end.brush", function(event) { emitter(this, arguments).end(event); }) + .tween("brush", function() { + var that = this, + state = that.__brush, + emit = emitter(that, arguments), + selection0 = state.selection, + selection1 = dim.input(typeof selection === "function" ? selection.apply(this, arguments) : selection, state.extent), + i = interpolate$2(selection0, selection1); + + function tween(t) { + state.selection = t === 1 && selection1 === null ? null : i(t); + redraw.call(that); + emit.brush(); + } + + return selection0 !== null && selection1 !== null ? tween : tween(1); + }); + } else { + group + .each(function() { + var that = this, + args = arguments, + state = that.__brush, + selection1 = dim.input(typeof selection === "function" ? selection.apply(that, args) : selection, state.extent), + emit = emitter(that, args).beforestart(); + + interrupt(that); + state.selection = selection1 === null ? null : selection1; + redraw.call(that); + emit.start(event).brush(event).end(event); + }); + } + }; + + brush.clear = function(group, event) { + brush.move(group, null, event); + }; + + function redraw() { + var group = select(this), + selection = local(this).selection; + + if (selection) { + group.selectAll(".selection") + .style("display", null) + .attr("x", selection[0][0]) + .attr("y", selection[0][1]) + .attr("width", selection[1][0] - selection[0][0]) + .attr("height", selection[1][1] - selection[0][1]); + + group.selectAll(".handle") + .style("display", null) + .attr("x", function(d) { return d.type[d.type.length - 1] === "e" ? selection[1][0] - handleSize / 2 : selection[0][0] - handleSize / 2; }) + .attr("y", function(d) { return d.type[0] === "s" ? selection[1][1] - handleSize / 2 : selection[0][1] - handleSize / 2; }) + .attr("width", function(d) { return d.type === "n" || d.type === "s" ? selection[1][0] - selection[0][0] + handleSize : handleSize; }) + .attr("height", function(d) { return d.type === "e" || d.type === "w" ? selection[1][1] - selection[0][1] + handleSize : handleSize; }); + } + + else { + group.selectAll(".selection,.handle") + .style("display", "none") + .attr("x", null) + .attr("y", null) + .attr("width", null) + .attr("height", null); + } + } + + function emitter(that, args, clean) { + var emit = that.__brush.emitter; + return emit && (!clean || !emit.clean) ? emit : new Emitter(that, args, clean); + } + + function Emitter(that, args, clean) { + this.that = that; + this.args = args; + this.state = that.__brush; + this.active = 0; + this.clean = clean; + } + + Emitter.prototype = { + beforestart: function() { + if (++this.active === 1) this.state.emitter = this, this.starting = true; + return this; + }, + start: function(event, mode) { + if (this.starting) this.starting = false, this.emit("start", event, mode); + else this.emit("brush", event); + return this; + }, + brush: function(event, mode) { + this.emit("brush", event, mode); + return this; + }, + end: function(event, mode) { + if (--this.active === 0) delete this.state.emitter, this.emit("end", event, mode); + return this; + }, + emit: function(type, event, mode) { + var d = select(this.that).datum(); + listeners.call( + type, + this.that, + new BrushEvent(type, { + sourceEvent: event, + target: brush, + selection: dim.output(this.state.selection), + mode, + dispatch: listeners + }), + d + ); + } + }; + + function started(event) { + if (touchending && !event.touches) return; + if (!filter.apply(this, arguments)) return; + + var that = this, + type = event.target.__data__.type, + mode = (keys && event.metaKey ? type = "overlay" : type) === "selection" ? MODE_DRAG : (keys && event.altKey ? MODE_CENTER : MODE_HANDLE), + signX = dim === Y ? null : signsX[type], + signY = dim === X ? null : signsY[type], + state = local(that), + extent = state.extent, + selection = state.selection, + W = extent[0][0], w0, w1, + N = extent[0][1], n0, n1, + E = extent[1][0], e0, e1, + S = extent[1][1], s0, s1, + dx = 0, + dy = 0, + moving, + shifting = signX && signY && keys && event.shiftKey, + lockX, + lockY, + points = Array.from(event.touches || [event], t => { + const i = t.identifier; + t = pointer(t, that); + t.point0 = t.slice(); + t.identifier = i; + return t; + }); + + interrupt(that); + var emit = emitter(that, arguments, true).beforestart(); + + if (type === "overlay") { + if (selection) moving = true; + const pts = [points[0], points[1] || points[0]]; + state.selection = selection = [[ + w0 = dim === Y ? W : min$1(pts[0][0], pts[1][0]), + n0 = dim === X ? N : min$1(pts[0][1], pts[1][1]) + ], [ + e0 = dim === Y ? E : max$2(pts[0][0], pts[1][0]), + s0 = dim === X ? S : max$2(pts[0][1], pts[1][1]) + ]]; + if (points.length > 1) move(event); + } else { + w0 = selection[0][0]; + n0 = selection[0][1]; + e0 = selection[1][0]; + s0 = selection[1][1]; + } + + w1 = w0; + n1 = n0; + e1 = e0; + s1 = s0; + + var group = select(that) + .attr("pointer-events", "none"); + + var overlay = group.selectAll(".overlay") + .attr("cursor", cursors[type]); + + if (event.touches) { + emit.moved = moved; + emit.ended = ended; + } else { + var view = select(event.view) + .on("mousemove.brush", moved, true) + .on("mouseup.brush", ended, true); + if (keys) view + .on("keydown.brush", keydowned, true) + .on("keyup.brush", keyupped, true); + + dragDisable(event.view); + } + + redraw.call(that); + emit.start(event, mode.name); + + function moved(event) { + for (const p of event.changedTouches || [event]) { + for (const d of points) + if (d.identifier === p.identifier) d.cur = pointer(p, that); + } + if (shifting && !lockX && !lockY && points.length === 1) { + const point = points[0]; + if (abs$3(point.cur[0] - point[0]) > abs$3(point.cur[1] - point[1])) + lockY = true; + else + lockX = true; + } + for (const point of points) + if (point.cur) point[0] = point.cur[0], point[1] = point.cur[1]; + moving = true; + noevent$1(event); + move(event); + } + + function move(event) { + const point = points[0], point0 = point.point0; + var t; + + dx = point[0] - point0[0]; + dy = point[1] - point0[1]; + + switch (mode) { + case MODE_SPACE: + case MODE_DRAG: { + if (signX) dx = max$2(W - w0, min$1(E - e0, dx)), w1 = w0 + dx, e1 = e0 + dx; + if (signY) dy = max$2(N - n0, min$1(S - s0, dy)), n1 = n0 + dy, s1 = s0 + dy; + break; + } + case MODE_HANDLE: { + if (points[1]) { + if (signX) w1 = max$2(W, min$1(E, points[0][0])), e1 = max$2(W, min$1(E, points[1][0])), signX = 1; + if (signY) n1 = max$2(N, min$1(S, points[0][1])), s1 = max$2(N, min$1(S, points[1][1])), signY = 1; + } else { + if (signX < 0) dx = max$2(W - w0, min$1(E - w0, dx)), w1 = w0 + dx, e1 = e0; + else if (signX > 0) dx = max$2(W - e0, min$1(E - e0, dx)), w1 = w0, e1 = e0 + dx; + if (signY < 0) dy = max$2(N - n0, min$1(S - n0, dy)), n1 = n0 + dy, s1 = s0; + else if (signY > 0) dy = max$2(N - s0, min$1(S - s0, dy)), n1 = n0, s1 = s0 + dy; + } + break; + } + case MODE_CENTER: { + if (signX) w1 = max$2(W, min$1(E, w0 - dx * signX)), e1 = max$2(W, min$1(E, e0 + dx * signX)); + if (signY) n1 = max$2(N, min$1(S, n0 - dy * signY)), s1 = max$2(N, min$1(S, s0 + dy * signY)); + break; + } + } + + if (e1 < w1) { + signX *= -1; + t = w0, w0 = e0, e0 = t; + t = w1, w1 = e1, e1 = t; + if (type in flipX) overlay.attr("cursor", cursors[type = flipX[type]]); + } + + if (s1 < n1) { + signY *= -1; + t = n0, n0 = s0, s0 = t; + t = n1, n1 = s1, s1 = t; + if (type in flipY) overlay.attr("cursor", cursors[type = flipY[type]]); + } + + if (state.selection) selection = state.selection; // May be set by brush.move! + if (lockX) w1 = selection[0][0], e1 = selection[1][0]; + if (lockY) n1 = selection[0][1], s1 = selection[1][1]; + + if (selection[0][0] !== w1 + || selection[0][1] !== n1 + || selection[1][0] !== e1 + || selection[1][1] !== s1) { + state.selection = [[w1, n1], [e1, s1]]; + redraw.call(that); + emit.brush(event, mode.name); + } + } + + function ended(event) { + nopropagation$1(event); + if (event.touches) { + if (event.touches.length) return; + if (touchending) clearTimeout(touchending); + touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed! + } else { + yesdrag(event.view, moving); + view.on("keydown.brush keyup.brush mousemove.brush mouseup.brush", null); + } + group.attr("pointer-events", "all"); + overlay.attr("cursor", cursors.overlay); + if (state.selection) selection = state.selection; // May be set by brush.move (on start)! + if (empty(selection)) state.selection = null, redraw.call(that); + emit.end(event, mode.name); + } + + function keydowned(event) { + switch (event.keyCode) { + case 16: { // SHIFT + shifting = signX && signY; + break; + } + case 18: { // ALT + if (mode === MODE_HANDLE) { + if (signX) e0 = e1 - dx * signX, w0 = w1 + dx * signX; + if (signY) s0 = s1 - dy * signY, n0 = n1 + dy * signY; + mode = MODE_CENTER; + move(event); + } + break; + } + case 32: { // SPACE; takes priority over ALT + if (mode === MODE_HANDLE || mode === MODE_CENTER) { + if (signX < 0) e0 = e1 - dx; else if (signX > 0) w0 = w1 - dx; + if (signY < 0) s0 = s1 - dy; else if (signY > 0) n0 = n1 - dy; + mode = MODE_SPACE; + overlay.attr("cursor", cursors.selection); + move(event); + } + break; + } + default: return; + } + noevent$1(event); + } + + function keyupped(event) { + switch (event.keyCode) { + case 16: { // SHIFT + if (shifting) { + lockX = lockY = shifting = false; + move(event); + } + break; + } + case 18: { // ALT + if (mode === MODE_CENTER) { + if (signX < 0) e0 = e1; else if (signX > 0) w0 = w1; + if (signY < 0) s0 = s1; else if (signY > 0) n0 = n1; + mode = MODE_HANDLE; + move(event); + } + break; + } + case 32: { // SPACE + if (mode === MODE_SPACE) { + if (event.altKey) { + if (signX) e0 = e1 - dx * signX, w0 = w1 + dx * signX; + if (signY) s0 = s1 - dy * signY, n0 = n1 + dy * signY; + mode = MODE_CENTER; + } else { + if (signX < 0) e0 = e1; else if (signX > 0) w0 = w1; + if (signY < 0) s0 = s1; else if (signY > 0) n0 = n1; + mode = MODE_HANDLE; + } + overlay.attr("cursor", cursors[type]); + move(event); + } + break; + } + default: return; + } + noevent$1(event); + } + } + + function touchmoved(event) { + emitter(this, arguments).moved(event); + } + + function touchended(event) { + emitter(this, arguments).ended(event); + } + + function initialize() { + var state = this.__brush || {selection: null}; + state.extent = number2(extent.apply(this, arguments)); + state.dim = dim; + return state; + } + + brush.extent = function(_) { + return arguments.length ? (extent = typeof _ === "function" ? _ : constant$7(number2(_)), brush) : extent; + }; + + brush.filter = function(_) { + return arguments.length ? (filter = typeof _ === "function" ? _ : constant$7(!!_), brush) : filter; + }; + + brush.touchable = function(_) { + return arguments.length ? (touchable = typeof _ === "function" ? _ : constant$7(!!_), brush) : touchable; + }; + + brush.handleSize = function(_) { + return arguments.length ? (handleSize = +_, brush) : handleSize; + }; + + brush.keyModifiers = function(_) { + return arguments.length ? (keys = !!_, brush) : keys; + }; + + brush.on = function() { + var value = listeners.on.apply(listeners, arguments); + return value === listeners ? brush : value; + }; + + return brush; +} + +var abs$2 = Math.abs; +var cos$2 = Math.cos; +var sin$2 = Math.sin; +var pi$3 = Math.PI; +var halfPi$2 = pi$3 / 2; +var tau$4 = pi$3 * 2; +var max$1 = Math.max; +var epsilon$5 = 1e-12; + +function range$1(i, j) { + return Array.from({length: j - i}, (_, k) => i + k); +} + +function compareValue(compare) { + return function(a, b) { + return compare( + a.source.value + a.target.value, + b.source.value + b.target.value + ); + }; +} + +function chord() { + return chord$1(false, false); +} + +function chordTranspose() { + return chord$1(false, true); +} + +function chordDirected() { + return chord$1(true, false); +} + +function chord$1(directed, transpose) { + var padAngle = 0, + sortGroups = null, + sortSubgroups = null, + sortChords = null; + + function chord(matrix) { + var n = matrix.length, + groupSums = new Array(n), + groupIndex = range$1(0, n), + chords = new Array(n * n), + groups = new Array(n), + k = 0, dx; + + matrix = Float64Array.from({length: n * n}, transpose + ? (_, i) => matrix[i % n][i / n | 0] + : (_, i) => matrix[i / n | 0][i % n]); + + // Compute the scaling factor from value to angle in [0, 2pi]. + for (let i = 0; i < n; ++i) { + let x = 0; + for (let j = 0; j < n; ++j) x += matrix[i * n + j] + directed * matrix[j * n + i]; + k += groupSums[i] = x; + } + k = max$1(0, tau$4 - padAngle * n) / k; + dx = k ? padAngle : tau$4 / n; + + // Compute the angles for each group and constituent chord. + { + let x = 0; + if (sortGroups) groupIndex.sort((a, b) => sortGroups(groupSums[a], groupSums[b])); + for (const i of groupIndex) { + const x0 = x; + if (directed) { + const subgroupIndex = range$1(~n + 1, n).filter(j => j < 0 ? matrix[~j * n + i] : matrix[i * n + j]); + if (sortSubgroups) subgroupIndex.sort((a, b) => sortSubgroups(a < 0 ? -matrix[~a * n + i] : matrix[i * n + a], b < 0 ? -matrix[~b * n + i] : matrix[i * n + b])); + for (const j of subgroupIndex) { + if (j < 0) { + const chord = chords[~j * n + i] || (chords[~j * n + i] = {source: null, target: null}); + chord.target = {index: i, startAngle: x, endAngle: x += matrix[~j * n + i] * k, value: matrix[~j * n + i]}; + } else { + const chord = chords[i * n + j] || (chords[i * n + j] = {source: null, target: null}); + chord.source = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; + } + } + groups[i] = {index: i, startAngle: x0, endAngle: x, value: groupSums[i]}; + } else { + const subgroupIndex = range$1(0, n).filter(j => matrix[i * n + j] || matrix[j * n + i]); + if (sortSubgroups) subgroupIndex.sort((a, b) => sortSubgroups(matrix[i * n + a], matrix[i * n + b])); + for (const j of subgroupIndex) { + let chord; + if (i < j) { + chord = chords[i * n + j] || (chords[i * n + j] = {source: null, target: null}); + chord.source = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; + } else { + chord = chords[j * n + i] || (chords[j * n + i] = {source: null, target: null}); + chord.target = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; + if (i === j) chord.source = chord.target; + } + if (chord.source && chord.target && chord.source.value < chord.target.value) { + const source = chord.source; + chord.source = chord.target; + chord.target = source; + } + } + groups[i] = {index: i, startAngle: x0, endAngle: x, value: groupSums[i]}; + } + x += dx; + } + } + + // Remove empty chords. + chords = Object.values(chords); + chords.groups = groups; + return sortChords ? chords.sort(sortChords) : chords; + } + + chord.padAngle = function(_) { + return arguments.length ? (padAngle = max$1(0, _), chord) : padAngle; + }; + + chord.sortGroups = function(_) { + return arguments.length ? (sortGroups = _, chord) : sortGroups; + }; + + chord.sortSubgroups = function(_) { + return arguments.length ? (sortSubgroups = _, chord) : sortSubgroups; + }; + + chord.sortChords = function(_) { + return arguments.length ? (_ == null ? sortChords = null : (sortChords = compareValue(_))._ = _, chord) : sortChords && sortChords._; + }; + + return chord; +} + +const pi$2 = Math.PI, + tau$3 = 2 * pi$2, + epsilon$4 = 1e-6, + tauEpsilon = tau$3 - epsilon$4; + +function append$1(strings) { + this._ += strings[0]; + for (let i = 1, n = strings.length; i < n; ++i) { + this._ += arguments[i] + strings[i]; + } +} + +function appendRound$1(digits) { + let d = Math.floor(digits); + if (!(d >= 0)) throw new Error(`invalid digits: ${digits}`); + if (d > 15) return append$1; + const k = 10 ** d; + return function(strings) { + this._ += strings[0]; + for (let i = 1, n = strings.length; i < n; ++i) { + this._ += Math.round(arguments[i] * k) / k + strings[i]; + } + }; +} + +let Path$1 = class Path { + constructor(digits) { + this._x0 = this._y0 = // start of current subpath + this._x1 = this._y1 = null; // end of current subpath + this._ = ""; + this._append = digits == null ? append$1 : appendRound$1(digits); + } + moveTo(x, y) { + this._append`M${this._x0 = this._x1 = +x},${this._y0 = this._y1 = +y}`; + } + closePath() { + if (this._x1 !== null) { + this._x1 = this._x0, this._y1 = this._y0; + this._append`Z`; + } + } + lineTo(x, y) { + this._append`L${this._x1 = +x},${this._y1 = +y}`; + } + quadraticCurveTo(x1, y1, x, y) { + this._append`Q${+x1},${+y1},${this._x1 = +x},${this._y1 = +y}`; + } + bezierCurveTo(x1, y1, x2, y2, x, y) { + this._append`C${+x1},${+y1},${+x2},${+y2},${this._x1 = +x},${this._y1 = +y}`; + } + arcTo(x1, y1, x2, y2, r) { + x1 = +x1, y1 = +y1, x2 = +x2, y2 = +y2, r = +r; + + // Is the radius negative? Error. + if (r < 0) throw new Error(`negative radius: ${r}`); + + let x0 = this._x1, + y0 = this._y1, + x21 = x2 - x1, + y21 = y2 - y1, + x01 = x0 - x1, + y01 = y0 - y1, + l01_2 = x01 * x01 + y01 * y01; + + // Is this path empty? Move to (x1,y1). + if (this._x1 === null) { + this._append`M${this._x1 = x1},${this._y1 = y1}`; + } + + // Or, is (x1,y1) coincident with (x0,y0)? Do nothing. + else if (!(l01_2 > epsilon$4)); + + // Or, are (x0,y0), (x1,y1) and (x2,y2) collinear? + // Equivalently, is (x1,y1) coincident with (x2,y2)? + // Or, is the radius zero? Line to (x1,y1). + else if (!(Math.abs(y01 * x21 - y21 * x01) > epsilon$4) || !r) { + this._append`L${this._x1 = x1},${this._y1 = y1}`; + } + + // Otherwise, draw an arc! + else { + let x20 = x2 - x0, + y20 = y2 - y0, + l21_2 = x21 * x21 + y21 * y21, + l20_2 = x20 * x20 + y20 * y20, + l21 = Math.sqrt(l21_2), + l01 = Math.sqrt(l01_2), + l = r * Math.tan((pi$2 - Math.acos((l21_2 + l01_2 - l20_2) / (2 * l21 * l01))) / 2), + t01 = l / l01, + t21 = l / l21; + + // If the start tangent is not coincident with (x0,y0), line to. + if (Math.abs(t01 - 1) > epsilon$4) { + this._append`L${x1 + t01 * x01},${y1 + t01 * y01}`; + } + + this._append`A${r},${r},0,0,${+(y01 * x20 > x01 * y20)},${this._x1 = x1 + t21 * x21},${this._y1 = y1 + t21 * y21}`; + } + } + arc(x, y, r, a0, a1, ccw) { + x = +x, y = +y, r = +r, ccw = !!ccw; + + // Is the radius negative? Error. + if (r < 0) throw new Error(`negative radius: ${r}`); + + let dx = r * Math.cos(a0), + dy = r * Math.sin(a0), + x0 = x + dx, + y0 = y + dy, + cw = 1 ^ ccw, + da = ccw ? a0 - a1 : a1 - a0; + + // Is this path empty? Move to (x0,y0). + if (this._x1 === null) { + this._append`M${x0},${y0}`; + } + + // Or, is (x0,y0) not coincident with the previous point? Line to (x0,y0). + else if (Math.abs(this._x1 - x0) > epsilon$4 || Math.abs(this._y1 - y0) > epsilon$4) { + this._append`L${x0},${y0}`; + } + + // Is this arc empty? We’re done. + if (!r) return; + + // Does the angle go the wrong way? Flip the direction. + if (da < 0) da = da % tau$3 + tau$3; + + // Is this a complete circle? Draw two arcs to complete the circle. + if (da > tauEpsilon) { + this._append`A${r},${r},0,1,${cw},${x - dx},${y - dy}A${r},${r},0,1,${cw},${this._x1 = x0},${this._y1 = y0}`; + } + + // Is this arc non-empty? Draw an arc! + else if (da > epsilon$4) { + this._append`A${r},${r},0,${+(da >= pi$2)},${cw},${this._x1 = x + r * Math.cos(a1)},${this._y1 = y + r * Math.sin(a1)}`; + } + } + rect(x, y, w, h) { + this._append`M${this._x0 = this._x1 = +x},${this._y0 = this._y1 = +y}h${w = +w}v${+h}h${-w}Z`; + } + toString() { + return this._; + } +}; + +function path() { + return new Path$1; +} + +// Allow instanceof d3.path +path.prototype = Path$1.prototype; + +function pathRound(digits = 3) { + return new Path$1(+digits); +} + +var slice$2 = Array.prototype.slice; + +function constant$6(x) { + return function() { + return x; + }; +} + +function defaultSource$1(d) { + return d.source; +} + +function defaultTarget(d) { + return d.target; +} + +function defaultRadius$1(d) { + return d.radius; +} + +function defaultStartAngle(d) { + return d.startAngle; +} + +function defaultEndAngle(d) { + return d.endAngle; +} + +function defaultPadAngle() { + return 0; +} + +function defaultArrowheadRadius() { + return 10; +} + +function ribbon(headRadius) { + var source = defaultSource$1, + target = defaultTarget, + sourceRadius = defaultRadius$1, + targetRadius = defaultRadius$1, + startAngle = defaultStartAngle, + endAngle = defaultEndAngle, + padAngle = defaultPadAngle, + context = null; + + function ribbon() { + var buffer, + s = source.apply(this, arguments), + t = target.apply(this, arguments), + ap = padAngle.apply(this, arguments) / 2, + argv = slice$2.call(arguments), + sr = +sourceRadius.apply(this, (argv[0] = s, argv)), + sa0 = startAngle.apply(this, argv) - halfPi$2, + sa1 = endAngle.apply(this, argv) - halfPi$2, + tr = +targetRadius.apply(this, (argv[0] = t, argv)), + ta0 = startAngle.apply(this, argv) - halfPi$2, + ta1 = endAngle.apply(this, argv) - halfPi$2; + + if (!context) context = buffer = path(); + + if (ap > epsilon$5) { + if (abs$2(sa1 - sa0) > ap * 2 + epsilon$5) sa1 > sa0 ? (sa0 += ap, sa1 -= ap) : (sa0 -= ap, sa1 += ap); + else sa0 = sa1 = (sa0 + sa1) / 2; + if (abs$2(ta1 - ta0) > ap * 2 + epsilon$5) ta1 > ta0 ? (ta0 += ap, ta1 -= ap) : (ta0 -= ap, ta1 += ap); + else ta0 = ta1 = (ta0 + ta1) / 2; + } + + context.moveTo(sr * cos$2(sa0), sr * sin$2(sa0)); + context.arc(0, 0, sr, sa0, sa1); + if (sa0 !== ta0 || sa1 !== ta1) { + if (headRadius) { + var hr = +headRadius.apply(this, arguments), tr2 = tr - hr, ta2 = (ta0 + ta1) / 2; + context.quadraticCurveTo(0, 0, tr2 * cos$2(ta0), tr2 * sin$2(ta0)); + context.lineTo(tr * cos$2(ta2), tr * sin$2(ta2)); + context.lineTo(tr2 * cos$2(ta1), tr2 * sin$2(ta1)); + } else { + context.quadraticCurveTo(0, 0, tr * cos$2(ta0), tr * sin$2(ta0)); + context.arc(0, 0, tr, ta0, ta1); + } + } + context.quadraticCurveTo(0, 0, sr * cos$2(sa0), sr * sin$2(sa0)); + context.closePath(); + + if (buffer) return context = null, buffer + "" || null; + } + + if (headRadius) ribbon.headRadius = function(_) { + return arguments.length ? (headRadius = typeof _ === "function" ? _ : constant$6(+_), ribbon) : headRadius; + }; + + ribbon.radius = function(_) { + return arguments.length ? (sourceRadius = targetRadius = typeof _ === "function" ? _ : constant$6(+_), ribbon) : sourceRadius; + }; + + ribbon.sourceRadius = function(_) { + return arguments.length ? (sourceRadius = typeof _ === "function" ? _ : constant$6(+_), ribbon) : sourceRadius; + }; + + ribbon.targetRadius = function(_) { + return arguments.length ? (targetRadius = typeof _ === "function" ? _ : constant$6(+_), ribbon) : targetRadius; + }; + + ribbon.startAngle = function(_) { + return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant$6(+_), ribbon) : startAngle; + }; + + ribbon.endAngle = function(_) { + return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant$6(+_), ribbon) : endAngle; + }; + + ribbon.padAngle = function(_) { + return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant$6(+_), ribbon) : padAngle; + }; + + ribbon.source = function(_) { + return arguments.length ? (source = _, ribbon) : source; + }; + + ribbon.target = function(_) { + return arguments.length ? (target = _, ribbon) : target; + }; + + ribbon.context = function(_) { + return arguments.length ? ((context = _ == null ? null : _), ribbon) : context; + }; + + return ribbon; +} + +function ribbon$1() { + return ribbon(); +} + +function ribbonArrow() { + return ribbon(defaultArrowheadRadius); +} + +var array$2 = Array.prototype; + +var slice$1 = array$2.slice; + +function ascending$1(a, b) { + return a - b; +} + +function area$3(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; +} + +var constant$5 = x => () => x; + +function contains$2(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$1(a, b, c) && within(a[i = +(a[0] === b[0])], c[i], b[i]); +} + +function collinear$1(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; +} + +function noop$2() {} + +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]]], + [] +]; + +function Contours() { + 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$1(values, finite); + tz = ticks(...nice$1(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$1); + } + + 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$3(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$2((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$5(slice$1.call(_)) : constant$5(_), contours) : threshold; + }; + + contours.smooth = function(_) { + return arguments.length ? (smooth = _ ? smoothLinear : noop$2, 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; +} + +function defaultX$1(d) { + return d[0]; +} + +function defaultY$1(d) { + return d[1]; +} + +function defaultWeight() { + return 1; +} + +function density() { + var x = defaultX$1, + y = defaultY$1, + 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$5(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$3(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$3(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$5(+_), density) : x; + }; + + density.y = function(_) { + return arguments.length ? (y = typeof _ === "function" ? _ : constant$5(+_), density) : y; + }; + + density.weight = function(_) { + return arguments.length ? (weight = typeof _ === "function" ? _ : constant$5(+_), 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$5(slice$1.call(_)) : constant$5(_), 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; +} + +const epsilon$3 = 1.1102230246251565e-16; +const splitter = 134217729; +const resulterrbound = (3 + 8 * epsilon$3) * epsilon$3; + +// fast_expansion_sum_zeroelim routine from oritinal code +function sum$1(elen, e, flen, f, h) { + let Q, Qnew, hh, bvirt; + let enow = e[0]; + let fnow = f[0]; + let eindex = 0; + let findex = 0; + if ((fnow > enow) === (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + let hindex = 0; + if (eindex < elen && findex < flen) { + if ((fnow > enow) === (fnow > -enow)) { + Qnew = enow + Q; + hh = Q - (Qnew - enow); + enow = e[++eindex]; + } else { + Qnew = fnow + Q; + hh = Q - (Qnew - fnow); + fnow = f[++findex]; + } + Q = Qnew; + if (hh !== 0) { + h[hindex++] = hh; + } + while (eindex < elen && findex < flen) { + if ((fnow > enow) === (fnow > -enow)) { + Qnew = Q + enow; + bvirt = Qnew - Q; + hh = Q - (Qnew - bvirt) + (enow - bvirt); + enow = e[++eindex]; + } else { + Qnew = Q + fnow; + bvirt = Qnew - Q; + hh = Q - (Qnew - bvirt) + (fnow - bvirt); + fnow = f[++findex]; + } + Q = Qnew; + if (hh !== 0) { + h[hindex++] = hh; + } + } + } + while (eindex < elen) { + Qnew = Q + enow; + bvirt = Qnew - Q; + hh = Q - (Qnew - bvirt) + (enow - bvirt); + enow = e[++eindex]; + Q = Qnew; + if (hh !== 0) { + h[hindex++] = hh; + } + } + while (findex < flen) { + Qnew = Q + fnow; + bvirt = Qnew - Q; + hh = Q - (Qnew - bvirt) + (fnow - bvirt); + fnow = f[++findex]; + Q = Qnew; + if (hh !== 0) { + h[hindex++] = hh; + } + } + if (Q !== 0 || hindex === 0) { + h[hindex++] = Q; + } + return hindex; +} + +function estimate(elen, e) { + let Q = e[0]; + for (let i = 1; i < elen; i++) Q += e[i]; + return Q; +} + +function vec(n) { + return new Float64Array(n); +} + +const ccwerrboundA = (3 + 16 * epsilon$3) * epsilon$3; +const ccwerrboundB = (2 + 12 * epsilon$3) * epsilon$3; +const ccwerrboundC = (9 + 64 * epsilon$3) * epsilon$3 * epsilon$3; + +const B = vec(4); +const C1 = vec(8); +const C2 = vec(12); +const D = vec(16); +const u = vec(4); + +function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) { + let acxtail, acytail, bcxtail, bcytail; + let bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3; + + const acx = ax - cx; + const bcx = bx - cx; + const acy = ay - cy; + const bcy = by - cy; + + s1 = acx * bcy; + c = splitter * acx; + ahi = c - (c - acx); + alo = acx - ahi; + c = splitter * bcy; + bhi = c - (c - bcy); + blo = bcy - bhi; + s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo); + t1 = acy * bcx; + c = splitter * acy; + ahi = c - (c - acy); + alo = acy - ahi; + c = splitter * bcx; + bhi = c - (c - bcx); + blo = bcx - bhi; + t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo); + _i = s0 - t0; + bvirt = s0 - _i; + B[0] = s0 - (_i + bvirt) + (bvirt - t0); + _j = s1 + _i; + bvirt = _j - s1; + _0 = s1 - (_j - bvirt) + (_i - bvirt); + _i = _0 - t1; + bvirt = _0 - _i; + B[1] = _0 - (_i + bvirt) + (bvirt - t1); + u3 = _j + _i; + bvirt = u3 - _j; + B[2] = _j - (u3 - bvirt) + (_i - bvirt); + B[3] = u3; + + let det = estimate(4, B); + let errbound = ccwerrboundB * detsum; + if (det >= errbound || -det >= errbound) { + return det; + } + + bvirt = ax - acx; + acxtail = ax - (acx + bvirt) + (bvirt - cx); + bvirt = bx - bcx; + bcxtail = bx - (bcx + bvirt) + (bvirt - cx); + bvirt = ay - acy; + acytail = ay - (acy + bvirt) + (bvirt - cy); + bvirt = by - bcy; + bcytail = by - (bcy + bvirt) + (bvirt - cy); + + if (acxtail === 0 && acytail === 0 && bcxtail === 0 && bcytail === 0) { + return det; + } + + errbound = ccwerrboundC * detsum + resulterrbound * Math.abs(det); + det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail); + if (det >= errbound || -det >= errbound) return det; + + s1 = acxtail * bcy; + c = splitter * acxtail; + ahi = c - (c - acxtail); + alo = acxtail - ahi; + c = splitter * bcy; + bhi = c - (c - bcy); + blo = bcy - bhi; + s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo); + t1 = acytail * bcx; + c = splitter * acytail; + ahi = c - (c - acytail); + alo = acytail - ahi; + c = splitter * bcx; + bhi = c - (c - bcx); + blo = bcx - bhi; + t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo); + _i = s0 - t0; + bvirt = s0 - _i; + u[0] = s0 - (_i + bvirt) + (bvirt - t0); + _j = s1 + _i; + bvirt = _j - s1; + _0 = s1 - (_j - bvirt) + (_i - bvirt); + _i = _0 - t1; + bvirt = _0 - _i; + u[1] = _0 - (_i + bvirt) + (bvirt - t1); + u3 = _j + _i; + bvirt = u3 - _j; + u[2] = _j - (u3 - bvirt) + (_i - bvirt); + u[3] = u3; + const C1len = sum$1(4, B, 4, u, C1); + + s1 = acx * bcytail; + c = splitter * acx; + ahi = c - (c - acx); + alo = acx - ahi; + c = splitter * bcytail; + bhi = c - (c - bcytail); + blo = bcytail - bhi; + s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo); + t1 = acy * bcxtail; + c = splitter * acy; + ahi = c - (c - acy); + alo = acy - ahi; + c = splitter * bcxtail; + bhi = c - (c - bcxtail); + blo = bcxtail - bhi; + t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo); + _i = s0 - t0; + bvirt = s0 - _i; + u[0] = s0 - (_i + bvirt) + (bvirt - t0); + _j = s1 + _i; + bvirt = _j - s1; + _0 = s1 - (_j - bvirt) + (_i - bvirt); + _i = _0 - t1; + bvirt = _0 - _i; + u[1] = _0 - (_i + bvirt) + (bvirt - t1); + u3 = _j + _i; + bvirt = u3 - _j; + u[2] = _j - (u3 - bvirt) + (_i - bvirt); + u[3] = u3; + const C2len = sum$1(C1len, C1, 4, u, C2); + + s1 = acxtail * bcytail; + c = splitter * acxtail; + ahi = c - (c - acxtail); + alo = acxtail - ahi; + c = splitter * bcytail; + bhi = c - (c - bcytail); + blo = bcytail - bhi; + s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo); + t1 = acytail * bcxtail; + c = splitter * acytail; + ahi = c - (c - acytail); + alo = acytail - ahi; + c = splitter * bcxtail; + bhi = c - (c - bcxtail); + blo = bcxtail - bhi; + t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo); + _i = s0 - t0; + bvirt = s0 - _i; + u[0] = s0 - (_i + bvirt) + (bvirt - t0); + _j = s1 + _i; + bvirt = _j - s1; + _0 = s1 - (_j - bvirt) + (_i - bvirt); + _i = _0 - t1; + bvirt = _0 - _i; + u[1] = _0 - (_i + bvirt) + (bvirt - t1); + u3 = _j + _i; + bvirt = u3 - _j; + u[2] = _j - (u3 - bvirt) + (_i - bvirt); + u[3] = u3; + const Dlen = sum$1(C2len, C2, 4, u, D); + + return D[Dlen - 1]; +} + +function orient2d(ax, ay, bx, by, cx, cy) { + const detleft = (ay - cy) * (bx - cx); + const detright = (ax - cx) * (by - cy); + const det = detleft - detright; + + const detsum = Math.abs(detleft + detright); + if (Math.abs(det) >= ccwerrboundA * detsum) return det; + + return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum); +} + +const EPSILON = Math.pow(2, -52); +const EDGE_STACK = new Uint32Array(512); + +class Delaunator { + + static from(points, getX = defaultGetX, getY = defaultGetY) { + const n = points.length; + const coords = new Float64Array(n * 2); + + for (let i = 0; i < n; i++) { + const p = points[i]; + coords[2 * i] = getX(p); + coords[2 * i + 1] = getY(p); + } + + return new Delaunator(coords); + } + + constructor(coords) { + const n = coords.length >> 1; + if (n > 0 && typeof coords[0] !== 'number') throw new Error('Expected coords to contain numbers.'); + + this.coords = coords; + + // arrays that will store the triangulation graph + const maxTriangles = Math.max(2 * n - 5, 0); + this._triangles = new Uint32Array(maxTriangles * 3); + this._halfedges = new Int32Array(maxTriangles * 3); + + // temporary arrays for tracking the edges of the advancing convex hull + this._hashSize = Math.ceil(Math.sqrt(n)); + this._hullPrev = new Uint32Array(n); // edge to prev edge + this._hullNext = new Uint32Array(n); // edge to next edge + this._hullTri = new Uint32Array(n); // edge to adjacent triangle + this._hullHash = new Int32Array(this._hashSize).fill(-1); // angular edge hash + + // temporary arrays for sorting points + this._ids = new Uint32Array(n); + this._dists = new Float64Array(n); + + this.update(); + } + + update() { + const {coords, _hullPrev: hullPrev, _hullNext: hullNext, _hullTri: hullTri, _hullHash: hullHash} = this; + const n = coords.length >> 1; + + // populate an array of point indices; calculate input data bbox + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + + for (let i = 0; i < n; i++) { + const x = coords[2 * i]; + const y = coords[2 * i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + this._ids[i] = i; + } + const cx = (minX + maxX) / 2; + const cy = (minY + maxY) / 2; + + let minDist = Infinity; + let i0, i1, i2; + + // pick a seed point close to the center + for (let i = 0; i < n; i++) { + const d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]); + if (d < minDist) { + i0 = i; + minDist = d; + } + } + const i0x = coords[2 * i0]; + const i0y = coords[2 * i0 + 1]; + + minDist = Infinity; + + // find the point closest to the seed + for (let i = 0; i < n; i++) { + if (i === i0) continue; + const d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1]); + if (d < minDist && d > 0) { + i1 = i; + minDist = d; + } + } + let i1x = coords[2 * i1]; + let i1y = coords[2 * i1 + 1]; + + let minRadius = Infinity; + + // find the third point which forms the smallest circumcircle with the first two + for (let i = 0; i < n; i++) { + if (i === i0 || i === i1) continue; + const r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1]); + if (r < minRadius) { + i2 = i; + minRadius = r; + } + } + let i2x = coords[2 * i2]; + let i2y = coords[2 * i2 + 1]; + + if (minRadius === Infinity) { + // order collinear points by dx (or dy if all x are identical) + // and return the list as a hull + for (let i = 0; i < n; i++) { + this._dists[i] = (coords[2 * i] - coords[0]) || (coords[2 * i + 1] - coords[1]); + } + quicksort(this._ids, this._dists, 0, n - 1); + const hull = new Uint32Array(n); + let j = 0; + for (let i = 0, d0 = -Infinity; i < n; i++) { + const id = this._ids[i]; + if (this._dists[id] > d0) { + hull[j++] = id; + d0 = this._dists[id]; + } + } + this.hull = hull.subarray(0, j); + this.triangles = new Uint32Array(0); + this.halfedges = new Uint32Array(0); + return; + } + + // swap the order of the seed points for counter-clockwise orientation + if (orient2d(i0x, i0y, i1x, i1y, i2x, i2y) < 0) { + const i = i1; + const x = i1x; + const y = i1y; + i1 = i2; + i1x = i2x; + i1y = i2y; + i2 = i; + i2x = x; + i2y = y; + } + + const center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y); + this._cx = center.x; + this._cy = center.y; + + for (let i = 0; i < n; i++) { + this._dists[i] = dist(coords[2 * i], coords[2 * i + 1], center.x, center.y); + } + + // sort the points by distance from the seed triangle circumcenter + quicksort(this._ids, this._dists, 0, n - 1); + + // set up the seed triangle as the starting hull + this._hullStart = i0; + let hullSize = 3; + + hullNext[i0] = hullPrev[i2] = i1; + hullNext[i1] = hullPrev[i0] = i2; + hullNext[i2] = hullPrev[i1] = i0; + + hullTri[i0] = 0; + hullTri[i1] = 1; + hullTri[i2] = 2; + + hullHash.fill(-1); + hullHash[this._hashKey(i0x, i0y)] = i0; + hullHash[this._hashKey(i1x, i1y)] = i1; + hullHash[this._hashKey(i2x, i2y)] = i2; + + this.trianglesLen = 0; + this._addTriangle(i0, i1, i2, -1, -1, -1); + + for (let k = 0, xp, yp; k < this._ids.length; k++) { + const i = this._ids[k]; + const x = coords[2 * i]; + const y = coords[2 * i + 1]; + + // skip near-duplicate points + if (k > 0 && Math.abs(x - xp) <= EPSILON && Math.abs(y - yp) <= EPSILON) continue; + xp = x; + yp = y; + + // skip seed triangle points + if (i === i0 || i === i1 || i === i2) continue; + + // find a visible edge on the convex hull using edge hash + let start = 0; + for (let j = 0, key = this._hashKey(x, y); j < this._hashSize; j++) { + start = hullHash[(key + j) % this._hashSize]; + if (start !== -1 && start !== hullNext[start]) break; + } + + start = hullPrev[start]; + let e = start, q; + while (q = hullNext[e], orient2d(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1]) >= 0) { + e = q; + if (e === start) { + e = -1; + break; + } + } + if (e === -1) continue; // likely a near-duplicate point; skip it + + // add the first triangle from the point + let t = this._addTriangle(e, i, hullNext[e], -1, -1, hullTri[e]); + + // recursively flip triangles from the point until they satisfy the Delaunay condition + hullTri[i] = this._legalize(t + 2); + hullTri[e] = t; // keep track of boundary triangles on the hull + hullSize++; + + // walk forward through the hull, adding more triangles and flipping recursively + let n = hullNext[e]; + while (q = hullNext[n], orient2d(x, y, coords[2 * n], coords[2 * n + 1], coords[2 * q], coords[2 * q + 1]) < 0) { + t = this._addTriangle(n, i, q, hullTri[i], -1, hullTri[n]); + hullTri[i] = this._legalize(t + 2); + hullNext[n] = n; // mark as removed + hullSize--; + n = q; + } + + // walk backward from the other side, adding more triangles and flipping + if (e === start) { + while (q = hullPrev[e], orient2d(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1]) < 0) { + t = this._addTriangle(q, i, e, -1, hullTri[e], hullTri[q]); + this._legalize(t + 2); + hullTri[q] = t; + hullNext[e] = e; // mark as removed + hullSize--; + e = q; + } + } + + // update the hull indices + this._hullStart = hullPrev[i] = e; + hullNext[e] = hullPrev[n] = i; + hullNext[i] = n; + + // save the two new edges in the hash table + hullHash[this._hashKey(x, y)] = i; + hullHash[this._hashKey(coords[2 * e], coords[2 * e + 1])] = e; + } + + this.hull = new Uint32Array(hullSize); + for (let i = 0, e = this._hullStart; i < hullSize; i++) { + this.hull[i] = e; + e = hullNext[e]; + } + + // trim typed triangle mesh arrays + this.triangles = this._triangles.subarray(0, this.trianglesLen); + this.halfedges = this._halfedges.subarray(0, this.trianglesLen); + } + + _hashKey(x, y) { + return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize; + } + + _legalize(a) { + const {_triangles: triangles, _halfedges: halfedges, coords} = this; + + let i = 0; + let ar = 0; + + // recursion eliminated with a fixed-size stack + while (true) { + const b = halfedges[a]; + + /* if the pair of triangles doesn't satisfy the Delaunay condition + * (p1 is inside the circumcircle of [p0, pl, pr]), flip them, + * then do the same check/flip recursively for the new pair of triangles + * + * pl pl + * /||\ / \ + * al/ || \bl al/ \a + * / || \ / \ + * / a||b \ flip /___ar___\ + * p0\ || /p1 => p0\---bl---/p1 + * \ || / \ / + * ar\ || /br b\ /br + * \||/ \ / + * pr pr + */ + const a0 = a - a % 3; + ar = a0 + (a + 2) % 3; + + if (b === -1) { // convex hull edge + if (i === 0) break; + a = EDGE_STACK[--i]; + continue; + } + + const b0 = b - b % 3; + const al = a0 + (a + 1) % 3; + const bl = b0 + (b + 2) % 3; + + const p0 = triangles[ar]; + const pr = triangles[a]; + const pl = triangles[al]; + const p1 = triangles[bl]; + + const illegal = inCircle( + coords[2 * p0], coords[2 * p0 + 1], + coords[2 * pr], coords[2 * pr + 1], + coords[2 * pl], coords[2 * pl + 1], + coords[2 * p1], coords[2 * p1 + 1]); + + if (illegal) { + triangles[a] = p1; + triangles[b] = p0; + + const hbl = halfedges[bl]; + + // edge swapped on the other side of the hull (rare); fix the halfedge reference + if (hbl === -1) { + let e = this._hullStart; + do { + if (this._hullTri[e] === bl) { + this._hullTri[e] = a; + break; + } + e = this._hullPrev[e]; + } while (e !== this._hullStart); + } + this._link(a, hbl); + this._link(b, halfedges[ar]); + this._link(ar, bl); + + const br = b0 + (b + 1) % 3; + + // don't worry about hitting the cap: it can only happen on extremely degenerate input + if (i < EDGE_STACK.length) { + EDGE_STACK[i++] = br; + } + } else { + if (i === 0) break; + a = EDGE_STACK[--i]; + } + } + + return ar; + } + + _link(a, b) { + this._halfedges[a] = b; + if (b !== -1) this._halfedges[b] = a; + } + + // add a new triangle given vertex indices and adjacent half-edge ids + _addTriangle(i0, i1, i2, a, b, c) { + const t = this.trianglesLen; + + this._triangles[t] = i0; + this._triangles[t + 1] = i1; + this._triangles[t + 2] = i2; + + this._link(t, a); + this._link(t + 1, b); + this._link(t + 2, c); + + this.trianglesLen += 3; + + return t; + } +} + +// monotonically increases with real angle, but doesn't need expensive trigonometry +function pseudoAngle(dx, dy) { + const p = dx / (Math.abs(dx) + Math.abs(dy)); + return (dy > 0 ? 3 - p : 1 + p) / 4; // [0..1] +} + +function dist(ax, ay, bx, by) { + const dx = ax - bx; + const dy = ay - by; + return dx * dx + dy * dy; +} + +function inCircle(ax, ay, bx, by, cx, cy, px, py) { + const dx = ax - px; + const dy = ay - py; + const ex = bx - px; + const ey = by - py; + const fx = cx - px; + const fy = cy - py; + + const ap = dx * dx + dy * dy; + const bp = ex * ex + ey * ey; + const cp = fx * fx + fy * fy; + + return dx * (ey * cp - bp * fy) - + dy * (ex * cp - bp * fx) + + ap * (ex * fy - ey * fx) < 0; +} + +function circumradius(ax, ay, bx, by, cx, cy) { + const dx = bx - ax; + const dy = by - ay; + const ex = cx - ax; + const ey = cy - ay; + + const bl = dx * dx + dy * dy; + const cl = ex * ex + ey * ey; + const d = 0.5 / (dx * ey - dy * ex); + + const x = (ey * bl - dy * cl) * d; + const y = (dx * cl - ex * bl) * d; + + return x * x + y * y; +} + +function circumcenter(ax, ay, bx, by, cx, cy) { + const dx = bx - ax; + const dy = by - ay; + const ex = cx - ax; + const ey = cy - ay; + + const bl = dx * dx + dy * dy; + const cl = ex * ex + ey * ey; + const d = 0.5 / (dx * ey - dy * ex); + + const x = ax + (ey * bl - dy * cl) * d; + const y = ay + (dx * cl - ex * bl) * d; + + return {x, y}; +} + +function quicksort(ids, dists, left, right) { + if (right - left <= 20) { + for (let i = left + 1; i <= right; i++) { + const temp = ids[i]; + const tempDist = dists[temp]; + let j = i - 1; + while (j >= left && dists[ids[j]] > tempDist) ids[j + 1] = ids[j--]; + ids[j + 1] = temp; + } + } else { + const median = (left + right) >> 1; + let i = left + 1; + let j = right; + swap(ids, median, i); + if (dists[ids[left]] > dists[ids[right]]) swap(ids, left, right); + if (dists[ids[i]] > dists[ids[right]]) swap(ids, i, right); + if (dists[ids[left]] > dists[ids[i]]) swap(ids, left, i); + + const temp = ids[i]; + const tempDist = dists[temp]; + while (true) { + do i++; while (dists[ids[i]] < tempDist); + do j--; while (dists[ids[j]] > tempDist); + if (j < i) break; + swap(ids, i, j); + } + ids[left + 1] = ids[j]; + ids[j] = temp; + + if (right - i + 1 >= j - left) { + quicksort(ids, dists, i, right); + quicksort(ids, dists, left, j - 1); + } else { + quicksort(ids, dists, left, j - 1); + quicksort(ids, dists, i, right); + } + } +} + +function swap(arr, i, j) { + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} + +function defaultGetX(p) { + return p[0]; +} +function defaultGetY(p) { + return p[1]; +} + +const epsilon$2 = 1e-6; + +class Path { + constructor() { + this._x0 = this._y0 = // start of current subpath + this._x1 = this._y1 = null; // end of current subpath + this._ = ""; + } + moveTo(x, y) { + this._ += `M${this._x0 = this._x1 = +x},${this._y0 = this._y1 = +y}`; + } + closePath() { + if (this._x1 !== null) { + this._x1 = this._x0, this._y1 = this._y0; + this._ += "Z"; + } + } + lineTo(x, y) { + this._ += `L${this._x1 = +x},${this._y1 = +y}`; + } + arc(x, y, r) { + x = +x, y = +y, r = +r; + const x0 = x + r; + const y0 = y; + if (r < 0) throw new Error("negative radius"); + if (this._x1 === null) this._ += `M${x0},${y0}`; + else if (Math.abs(this._x1 - x0) > epsilon$2 || Math.abs(this._y1 - y0) > epsilon$2) this._ += "L" + x0 + "," + y0; + if (!r) return; + this._ += `A${r},${r},0,1,1,${x - r},${y}A${r},${r},0,1,1,${this._x1 = x0},${this._y1 = y0}`; + } + rect(x, y, w, h) { + this._ += `M${this._x0 = this._x1 = +x},${this._y0 = this._y1 = +y}h${+w}v${+h}h${-w}Z`; + } + value() { + return this._ || null; + } +} + +class Polygon { + constructor() { + this._ = []; + } + moveTo(x, y) { + this._.push([x, y]); + } + closePath() { + this._.push(this._[0].slice()); + } + lineTo(x, y) { + this._.push([x, y]); + } + value() { + return this._.length ? this._ : null; + } +} + +class Voronoi { + constructor(delaunay, [xmin, ymin, xmax, ymax] = [0, 0, 960, 500]) { + if (!((xmax = +xmax) >= (xmin = +xmin)) || !((ymax = +ymax) >= (ymin = +ymin))) throw new Error("invalid bounds"); + this.delaunay = delaunay; + this._circumcenters = new Float64Array(delaunay.points.length * 2); + this.vectors = new Float64Array(delaunay.points.length * 2); + this.xmax = xmax, this.xmin = xmin; + this.ymax = ymax, this.ymin = ymin; + this._init(); + } + update() { + this.delaunay.update(); + this._init(); + return this; + } + _init() { + const {delaunay: {points, hull, triangles}, vectors} = this; + let bx, by; // lazily computed barycenter of the hull + + // Compute circumcenters. + const circumcenters = this.circumcenters = this._circumcenters.subarray(0, triangles.length / 3 * 2); + for (let i = 0, j = 0, n = triangles.length, x, y; i < n; i += 3, j += 2) { + const t1 = triangles[i] * 2; + const t2 = triangles[i + 1] * 2; + const t3 = triangles[i + 2] * 2; + const x1 = points[t1]; + const y1 = points[t1 + 1]; + const x2 = points[t2]; + const y2 = points[t2 + 1]; + const x3 = points[t3]; + const y3 = points[t3 + 1]; + + const dx = x2 - x1; + const dy = y2 - y1; + const ex = x3 - x1; + const ey = y3 - y1; + const ab = (dx * ey - dy * ex) * 2; + + if (Math.abs(ab) < 1e-9) { + // For a degenerate triangle, the circumcenter is at the infinity, in a + // direction orthogonal to the halfedge and away from the “center” of + // the diagram <bx, by>, defined as the hull’s barycenter. + if (bx === undefined) { + bx = by = 0; + for (const i of hull) bx += points[i * 2], by += points[i * 2 + 1]; + bx /= hull.length, by /= hull.length; + } + const a = 1e9 * Math.sign((bx - x1) * ey - (by - y1) * ex); + x = (x1 + x3) / 2 - a * ey; + y = (y1 + y3) / 2 + a * ex; + } else { + const d = 1 / ab; + const bl = dx * dx + dy * dy; + const cl = ex * ex + ey * ey; + x = x1 + (ey * bl - dy * cl) * d; + y = y1 + (dx * cl - ex * bl) * d; + } + circumcenters[j] = x; + circumcenters[j + 1] = y; + } + + // Compute exterior cell rays. + let h = hull[hull.length - 1]; + let p0, p1 = h * 4; + let x0, x1 = points[2 * h]; + let y0, y1 = points[2 * h + 1]; + vectors.fill(0); + for (let i = 0; i < hull.length; ++i) { + h = hull[i]; + p0 = p1, x0 = x1, y0 = y1; + p1 = h * 4, x1 = points[2 * h], y1 = points[2 * h + 1]; + vectors[p0 + 2] = vectors[p1] = y0 - y1; + vectors[p0 + 3] = vectors[p1 + 1] = x1 - x0; + } + } + render(context) { + const buffer = context == null ? context = new Path : undefined; + const {delaunay: {halfedges, inedges, hull}, circumcenters, vectors} = this; + if (hull.length <= 1) return null; + for (let i = 0, n = halfedges.length; i < n; ++i) { + const j = halfedges[i]; + if (j < i) continue; + const ti = Math.floor(i / 3) * 2; + const tj = Math.floor(j / 3) * 2; + const xi = circumcenters[ti]; + const yi = circumcenters[ti + 1]; + const xj = circumcenters[tj]; + const yj = circumcenters[tj + 1]; + this._renderSegment(xi, yi, xj, yj, context); + } + let h0, h1 = hull[hull.length - 1]; + for (let i = 0; i < hull.length; ++i) { + h0 = h1, h1 = hull[i]; + const t = Math.floor(inedges[h1] / 3) * 2; + const x = circumcenters[t]; + const y = circumcenters[t + 1]; + const v = h0 * 4; + const p = this._project(x, y, vectors[v + 2], vectors[v + 3]); + if (p) this._renderSegment(x, y, p[0], p[1], context); + } + return buffer && buffer.value(); + } + renderBounds(context) { + const buffer = context == null ? context = new Path : undefined; + context.rect(this.xmin, this.ymin, this.xmax - this.xmin, this.ymax - this.ymin); + return buffer && buffer.value(); + } + renderCell(i, context) { + const buffer = context == null ? context = new Path : undefined; + const points = this._clip(i); + if (points === null || !points.length) return; + context.moveTo(points[0], points[1]); + let n = points.length; + while (points[0] === points[n-2] && points[1] === points[n-1] && n > 1) n -= 2; + for (let i = 2; i < n; i += 2) { + if (points[i] !== points[i-2] || points[i+1] !== points[i-1]) + context.lineTo(points[i], points[i + 1]); + } + context.closePath(); + return buffer && buffer.value(); + } + *cellPolygons() { + const {delaunay: {points}} = this; + for (let i = 0, n = points.length / 2; i < n; ++i) { + const cell = this.cellPolygon(i); + if (cell) cell.index = i, yield cell; + } + } + cellPolygon(i) { + const polygon = new Polygon; + this.renderCell(i, polygon); + return polygon.value(); + } + _renderSegment(x0, y0, x1, y1, context) { + let S; + const c0 = this._regioncode(x0, y0); + const c1 = this._regioncode(x1, y1); + if (c0 === 0 && c1 === 0) { + context.moveTo(x0, y0); + context.lineTo(x1, y1); + } else if (S = this._clipSegment(x0, y0, x1, y1, c0, c1)) { + context.moveTo(S[0], S[1]); + context.lineTo(S[2], S[3]); + } + } + contains(i, x, y) { + if ((x = +x, x !== x) || (y = +y, y !== y)) return false; + return this.delaunay._step(i, x, y) === i; + } + *neighbors(i) { + const ci = this._clip(i); + if (ci) for (const j of this.delaunay.neighbors(i)) { + const cj = this._clip(j); + // find the common edge + if (cj) loop: for (let ai = 0, li = ci.length; ai < li; ai += 2) { + for (let aj = 0, lj = cj.length; aj < lj; aj += 2) { + if (ci[ai] === cj[aj] + && ci[ai + 1] === cj[aj + 1] + && ci[(ai + 2) % li] === cj[(aj + lj - 2) % lj] + && ci[(ai + 3) % li] === cj[(aj + lj - 1) % lj]) { + yield j; + break loop; + } + } + } + } + } + _cell(i) { + const {circumcenters, delaunay: {inedges, halfedges, triangles}} = this; + const e0 = inedges[i]; + if (e0 === -1) return null; // coincident point + const points = []; + let e = e0; + do { + const t = Math.floor(e / 3); + points.push(circumcenters[t * 2], circumcenters[t * 2 + 1]); + e = e % 3 === 2 ? e - 2 : e + 1; + if (triangles[e] !== i) break; // bad triangulation + e = halfedges[e]; + } while (e !== e0 && e !== -1); + return points; + } + _clip(i) { + // degenerate case (1 valid point: return the box) + if (i === 0 && this.delaunay.hull.length === 1) { + return [this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax, this.xmin, this.ymin]; + } + const points = this._cell(i); + if (points === null) return null; + const {vectors: V} = this; + const v = i * 4; + return this._simplify(V[v] || V[v + 1] + ? this._clipInfinite(i, points, V[v], V[v + 1], V[v + 2], V[v + 3]) + : this._clipFinite(i, points)); + } + _clipFinite(i, points) { + const n = points.length; + let P = null; + let x0, y0, x1 = points[n - 2], y1 = points[n - 1]; + let c0, c1 = this._regioncode(x1, y1); + let e0, e1 = 0; + for (let j = 0; j < n; j += 2) { + x0 = x1, y0 = y1, x1 = points[j], y1 = points[j + 1]; + c0 = c1, c1 = this._regioncode(x1, y1); + if (c0 === 0 && c1 === 0) { + e0 = e1, e1 = 0; + if (P) P.push(x1, y1); + else P = [x1, y1]; + } else { + let S, sx0, sy0, sx1, sy1; + if (c0 === 0) { + if ((S = this._clipSegment(x0, y0, x1, y1, c0, c1)) === null) continue; + [sx0, sy0, sx1, sy1] = S; + } else { + if ((S = this._clipSegment(x1, y1, x0, y0, c1, c0)) === null) continue; + [sx1, sy1, sx0, sy0] = S; + e0 = e1, e1 = this._edgecode(sx0, sy0); + if (e0 && e1) this._edge(i, e0, e1, P, P.length); + if (P) P.push(sx0, sy0); + else P = [sx0, sy0]; + } + e0 = e1, e1 = this._edgecode(sx1, sy1); + if (e0 && e1) this._edge(i, e0, e1, P, P.length); + if (P) P.push(sx1, sy1); + else P = [sx1, sy1]; + } + } + if (P) { + e0 = e1, e1 = this._edgecode(P[0], P[1]); + if (e0 && e1) this._edge(i, e0, e1, P, P.length); + } else if (this.contains(i, (this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2)) { + return [this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax, this.xmin, this.ymin]; + } + return P; + } + _clipSegment(x0, y0, x1, y1, c0, c1) { + // for more robustness, always consider the segment in the same order + const flip = c0 < c1; + if (flip) [x0, y0, x1, y1, c0, c1] = [x1, y1, x0, y0, c1, c0]; + while (true) { + if (c0 === 0 && c1 === 0) return flip ? [x1, y1, x0, y0] : [x0, y0, x1, y1]; + if (c0 & c1) return null; + let x, y, c = c0 || c1; + if (c & 0b1000) x = x0 + (x1 - x0) * (this.ymax - y0) / (y1 - y0), y = this.ymax; + else if (c & 0b0100) x = x0 + (x1 - x0) * (this.ymin - y0) / (y1 - y0), y = this.ymin; + else if (c & 0b0010) y = y0 + (y1 - y0) * (this.xmax - x0) / (x1 - x0), x = this.xmax; + else y = y0 + (y1 - y0) * (this.xmin - x0) / (x1 - x0), x = this.xmin; + if (c0) x0 = x, y0 = y, c0 = this._regioncode(x0, y0); + else x1 = x, y1 = y, c1 = this._regioncode(x1, y1); + } + } + _clipInfinite(i, points, vx0, vy0, vxn, vyn) { + let P = Array.from(points), p; + if (p = this._project(P[0], P[1], vx0, vy0)) P.unshift(p[0], p[1]); + if (p = this._project(P[P.length - 2], P[P.length - 1], vxn, vyn)) P.push(p[0], p[1]); + if (P = this._clipFinite(i, P)) { + for (let j = 0, n = P.length, c0, c1 = this._edgecode(P[n - 2], P[n - 1]); j < n; j += 2) { + c0 = c1, c1 = this._edgecode(P[j], P[j + 1]); + if (c0 && c1) j = this._edge(i, c0, c1, P, j), n = P.length; + } + } else if (this.contains(i, (this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2)) { + P = [this.xmin, this.ymin, this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax]; + } + return P; + } + _edge(i, e0, e1, P, j) { + while (e0 !== e1) { + let x, y; + switch (e0) { + case 0b0101: e0 = 0b0100; continue; // top-left + case 0b0100: e0 = 0b0110, x = this.xmax, y = this.ymin; break; // top + case 0b0110: e0 = 0b0010; continue; // top-right + case 0b0010: e0 = 0b1010, x = this.xmax, y = this.ymax; break; // right + case 0b1010: e0 = 0b1000; continue; // bottom-right + case 0b1000: e0 = 0b1001, x = this.xmin, y = this.ymax; break; // bottom + case 0b1001: e0 = 0b0001; continue; // bottom-left + case 0b0001: e0 = 0b0101, x = this.xmin, y = this.ymin; break; // left + } + // Note: this implicitly checks for out of bounds: if P[j] or P[j+1] are + // undefined, the conditional statement will be executed. + if ((P[j] !== x || P[j + 1] !== y) && this.contains(i, x, y)) { + P.splice(j, 0, x, y), j += 2; + } + } + return j; + } + _project(x0, y0, vx, vy) { + let t = Infinity, c, x, y; + if (vy < 0) { // top + if (y0 <= this.ymin) return null; + if ((c = (this.ymin - y0) / vy) < t) y = this.ymin, x = x0 + (t = c) * vx; + } else if (vy > 0) { // bottom + if (y0 >= this.ymax) return null; + if ((c = (this.ymax - y0) / vy) < t) y = this.ymax, x = x0 + (t = c) * vx; + } + if (vx > 0) { // right + if (x0 >= this.xmax) return null; + if ((c = (this.xmax - x0) / vx) < t) x = this.xmax, y = y0 + (t = c) * vy; + } else if (vx < 0) { // left + if (x0 <= this.xmin) return null; + if ((c = (this.xmin - x0) / vx) < t) x = this.xmin, y = y0 + (t = c) * vy; + } + return [x, y]; + } + _edgecode(x, y) { + return (x === this.xmin ? 0b0001 + : x === this.xmax ? 0b0010 : 0b0000) + | (y === this.ymin ? 0b0100 + : y === this.ymax ? 0b1000 : 0b0000); + } + _regioncode(x, y) { + return (x < this.xmin ? 0b0001 + : x > this.xmax ? 0b0010 : 0b0000) + | (y < this.ymin ? 0b0100 + : y > this.ymax ? 0b1000 : 0b0000); + } + _simplify(P) { + if (P && P.length > 4) { + for (let i = 0; i < P.length; i+= 2) { + const j = (i + 2) % P.length, k = (i + 4) % P.length; + if (P[i] === P[j] && P[j] === P[k] || P[i + 1] === P[j + 1] && P[j + 1] === P[k + 1]) { + P.splice(j, 2), i -= 2; + } + } + if (!P.length) P = null; + } + return P; + } +} + +const tau$2 = 2 * Math.PI, pow$2 = Math.pow; + +function pointX(p) { + return p[0]; +} + +function pointY(p) { + return p[1]; +} + +// A triangulation is collinear if all its triangles have a non-null area +function collinear(d) { + const {triangles, coords} = d; + for (let i = 0; i < triangles.length; i += 3) { + const a = 2 * triangles[i], + b = 2 * triangles[i + 1], + c = 2 * triangles[i + 2], + cross = (coords[c] - coords[a]) * (coords[b + 1] - coords[a + 1]) + - (coords[b] - coords[a]) * (coords[c + 1] - coords[a + 1]); + if (cross > 1e-10) return false; + } + return true; +} + +function jitter(x, y, r) { + return [x + Math.sin(x + y) * r, y + Math.cos(x - y) * r]; +} + +class Delaunay { + static from(points, fx = pointX, fy = pointY, that) { + return new Delaunay("length" in points + ? flatArray(points, fx, fy, that) + : Float64Array.from(flatIterable(points, fx, fy, that))); + } + constructor(points) { + this._delaunator = new Delaunator(points); + this.inedges = new Int32Array(points.length / 2); + this._hullIndex = new Int32Array(points.length / 2); + this.points = this._delaunator.coords; + this._init(); + } + update() { + this._delaunator.update(); + this._init(); + return this; + } + _init() { + const d = this._delaunator, points = this.points; + + // check for collinear + if (d.hull && d.hull.length > 2 && collinear(d)) { + this.collinear = Int32Array.from({length: points.length/2}, (_,i) => i) + .sort((i, j) => points[2 * i] - points[2 * j] || points[2 * i + 1] - points[2 * j + 1]); // for exact neighbors + const e = this.collinear[0], f = this.collinear[this.collinear.length - 1], + bounds = [ points[2 * e], points[2 * e + 1], points[2 * f], points[2 * f + 1] ], + r = 1e-8 * Math.hypot(bounds[3] - bounds[1], bounds[2] - bounds[0]); + for (let i = 0, n = points.length / 2; i < n; ++i) { + const p = jitter(points[2 * i], points[2 * i + 1], r); + points[2 * i] = p[0]; + points[2 * i + 1] = p[1]; + } + this._delaunator = new Delaunator(points); + } else { + delete this.collinear; + } + + const halfedges = this.halfedges = this._delaunator.halfedges; + const hull = this.hull = this._delaunator.hull; + const triangles = this.triangles = this._delaunator.triangles; + const inedges = this.inedges.fill(-1); + const hullIndex = this._hullIndex.fill(-1); + + // Compute an index from each point to an (arbitrary) incoming halfedge + // Used to give the first neighbor of each point; for this reason, + // on the hull we give priority to exterior halfedges + for (let e = 0, n = halfedges.length; e < n; ++e) { + const p = triangles[e % 3 === 2 ? e - 2 : e + 1]; + if (halfedges[e] === -1 || inedges[p] === -1) inedges[p] = e; + } + for (let i = 0, n = hull.length; i < n; ++i) { + hullIndex[hull[i]] = i; + } + + // degenerate case: 1 or 2 (distinct) points + if (hull.length <= 2 && hull.length > 0) { + this.triangles = new Int32Array(3).fill(-1); + this.halfedges = new Int32Array(3).fill(-1); + this.triangles[0] = hull[0]; + inedges[hull[0]] = 1; + if (hull.length === 2) { + inedges[hull[1]] = 0; + this.triangles[1] = hull[1]; + this.triangles[2] = hull[1]; + } + } + } + voronoi(bounds) { + return new Voronoi(this, bounds); + } + *neighbors(i) { + const {inedges, hull, _hullIndex, halfedges, triangles, collinear} = this; + + // degenerate case with several collinear points + if (collinear) { + const l = collinear.indexOf(i); + if (l > 0) yield collinear[l - 1]; + if (l < collinear.length - 1) yield collinear[l + 1]; + return; + } + + const e0 = inedges[i]; + if (e0 === -1) return; // coincident point + let e = e0, p0 = -1; + do { + yield p0 = triangles[e]; + e = e % 3 === 2 ? e - 2 : e + 1; + if (triangles[e] !== i) return; // bad triangulation + e = halfedges[e]; + if (e === -1) { + const p = hull[(_hullIndex[i] + 1) % hull.length]; + if (p !== p0) yield p; + return; + } + } while (e !== e0); + } + find(x, y, i = 0) { + if ((x = +x, x !== x) || (y = +y, y !== y)) return -1; + const i0 = i; + let c; + while ((c = this._step(i, x, y)) >= 0 && c !== i && c !== i0) i = c; + return c; + } + _step(i, x, y) { + const {inedges, hull, _hullIndex, halfedges, triangles, points} = this; + if (inedges[i] === -1 || !points.length) return (i + 1) % (points.length >> 1); + let c = i; + let dc = pow$2(x - points[i * 2], 2) + pow$2(y - points[i * 2 + 1], 2); + const e0 = inedges[i]; + let e = e0; + do { + let t = triangles[e]; + const dt = pow$2(x - points[t * 2], 2) + pow$2(y - points[t * 2 + 1], 2); + if (dt < dc) dc = dt, c = t; + e = e % 3 === 2 ? e - 2 : e + 1; + if (triangles[e] !== i) break; // bad triangulation + e = halfedges[e]; + if (e === -1) { + e = hull[(_hullIndex[i] + 1) % hull.length]; + if (e !== t) { + if (pow$2(x - points[e * 2], 2) + pow$2(y - points[e * 2 + 1], 2) < dc) return e; + } + break; + } + } while (e !== e0); + return c; + } + render(context) { + const buffer = context == null ? context = new Path : undefined; + const {points, halfedges, triangles} = this; + for (let i = 0, n = halfedges.length; i < n; ++i) { + const j = halfedges[i]; + if (j < i) continue; + const ti = triangles[i] * 2; + const tj = triangles[j] * 2; + context.moveTo(points[ti], points[ti + 1]); + context.lineTo(points[tj], points[tj + 1]); + } + this.renderHull(context); + return buffer && buffer.value(); + } + renderPoints(context, r) { + if (r === undefined && (!context || typeof context.moveTo !== "function")) r = context, context = null; + r = r == undefined ? 2 : +r; + const buffer = context == null ? context = new Path : undefined; + const {points} = this; + for (let i = 0, n = points.length; i < n; i += 2) { + const x = points[i], y = points[i + 1]; + context.moveTo(x + r, y); + context.arc(x, y, r, 0, tau$2); + } + return buffer && buffer.value(); + } + renderHull(context) { + const buffer = context == null ? context = new Path : undefined; + const {hull, points} = this; + const h = hull[0] * 2, n = hull.length; + context.moveTo(points[h], points[h + 1]); + for (let i = 1; i < n; ++i) { + const h = 2 * hull[i]; + context.lineTo(points[h], points[h + 1]); + } + context.closePath(); + return buffer && buffer.value(); + } + hullPolygon() { + const polygon = new Polygon; + this.renderHull(polygon); + return polygon.value(); + } + renderTriangle(i, context) { + const buffer = context == null ? context = new Path : undefined; + const {points, triangles} = this; + const t0 = triangles[i *= 3] * 2; + const t1 = triangles[i + 1] * 2; + const t2 = triangles[i + 2] * 2; + context.moveTo(points[t0], points[t0 + 1]); + context.lineTo(points[t1], points[t1 + 1]); + context.lineTo(points[t2], points[t2 + 1]); + context.closePath(); + return buffer && buffer.value(); + } + *trianglePolygons() { + const {triangles} = this; + for (let i = 0, n = triangles.length / 3; i < n; ++i) { + yield this.trianglePolygon(i); + } + } + trianglePolygon(i) { + const polygon = new Polygon; + this.renderTriangle(i, polygon); + return polygon.value(); + } +} + +function flatArray(points, fx, fy, that) { + const n = points.length; + const array = new Float64Array(n * 2); + for (let i = 0; i < n; ++i) { + const p = points[i]; + array[i * 2] = fx.call(that, p, i, points); + array[i * 2 + 1] = fy.call(that, p, i, points); + } + return array; +} + +function* flatIterable(points, fx, fy, that) { + let i = 0; + for (const p of points) { + yield fx.call(that, p, i, points); + yield fy.call(that, p, i, points); + ++i; + } +} + +var EOL = {}, + EOF = {}, + QUOTE = 34, + NEWLINE = 10, + RETURN = 13; + +function objectConverter(columns) { + return new Function("d", "return {" + columns.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "] || \"\""; + }).join(",") + "}"); +} + +function customConverter(columns, f) { + var object = objectConverter(columns); + return function(row, i) { + return f(object(row), i, columns); + }; +} + +// Compute unique columns in order of discovery. +function inferColumns(rows) { + var columnSet = Object.create(null), + columns = []; + + rows.forEach(function(row) { + for (var column in row) { + if (!(column in columnSet)) { + columns.push(columnSet[column] = column); + } + } + }); + + return columns; +} + +function pad$1(value, width) { + var s = value + "", length = s.length; + return length < width ? new Array(width - length + 1).join(0) + s : s; +} + +function formatYear$1(year) { + return year < 0 ? "-" + pad$1(-year, 6) + : year > 9999 ? "+" + pad$1(year, 6) + : pad$1(year, 4); +} + +function formatDate(date) { + var hours = date.getUTCHours(), + minutes = date.getUTCMinutes(), + seconds = date.getUTCSeconds(), + milliseconds = date.getUTCMilliseconds(); + return isNaN(date) ? "Invalid Date" + : formatYear$1(date.getUTCFullYear()) + "-" + pad$1(date.getUTCMonth() + 1, 2) + "-" + pad$1(date.getUTCDate(), 2) + + (milliseconds ? "T" + pad$1(hours, 2) + ":" + pad$1(minutes, 2) + ":" + pad$1(seconds, 2) + "." + pad$1(milliseconds, 3) + "Z" + : seconds ? "T" + pad$1(hours, 2) + ":" + pad$1(minutes, 2) + ":" + pad$1(seconds, 2) + "Z" + : minutes || hours ? "T" + pad$1(hours, 2) + ":" + pad$1(minutes, 2) + "Z" + : ""); +} + +function dsvFormat(delimiter) { + var reFormat = new RegExp("[\"" + delimiter + "\n\r]"), + DELIMITER = delimiter.charCodeAt(0); + + function parse(text, f) { + var convert, columns, rows = parseRows(text, function(row, i) { + if (convert) return convert(row, i - 1); + columns = row, convert = f ? customConverter(row, f) : objectConverter(row); + }); + rows.columns = columns || []; + return rows; + } + + function parseRows(text, f) { + var rows = [], // output rows + N = text.length, + I = 0, // current character index + n = 0, // current line number + t, // current token + eof = N <= 0, // current token followed by EOF? + eol = false; // current token followed by EOL? + + // Strip the trailing newline. + if (text.charCodeAt(N - 1) === NEWLINE) --N; + if (text.charCodeAt(N - 1) === RETURN) --N; + + function token() { + if (eof) return EOF; + if (eol) return eol = false, EOL; + + // Unescape quotes. + var i, j = I, c; + if (text.charCodeAt(j) === QUOTE) { + while (I++ < N && text.charCodeAt(I) !== QUOTE || text.charCodeAt(++I) === QUOTE); + if ((i = I) >= N) eof = true; + else if ((c = text.charCodeAt(I++)) === NEWLINE) eol = true; + else if (c === RETURN) { eol = true; if (text.charCodeAt(I) === NEWLINE) ++I; } + return text.slice(j + 1, i - 1).replace(/""/g, "\""); + } + + // Find next delimiter or newline. + while (I < N) { + if ((c = text.charCodeAt(i = I++)) === NEWLINE) eol = true; + else if (c === RETURN) { eol = true; if (text.charCodeAt(I) === NEWLINE) ++I; } + else if (c !== DELIMITER) continue; + return text.slice(j, i); + } + + // Return last token before EOF. + return eof = true, text.slice(j, N); + } + + while ((t = token()) !== EOF) { + var row = []; + while (t !== EOL && t !== EOF) row.push(t), t = token(); + if (f && (row = f(row, n++)) == null) continue; + rows.push(row); + } + + return rows; + } + + function preformatBody(rows, columns) { + return rows.map(function(row) { + return columns.map(function(column) { + return formatValue(row[column]); + }).join(delimiter); + }); + } + + function format(rows, columns) { + if (columns == null) columns = inferColumns(rows); + return [columns.map(formatValue).join(delimiter)].concat(preformatBody(rows, columns)).join("\n"); + } + + function formatBody(rows, columns) { + if (columns == null) columns = inferColumns(rows); + return preformatBody(rows, columns).join("\n"); + } + + function formatRows(rows) { + return rows.map(formatRow).join("\n"); + } + + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + + function formatValue(value) { + return value == null ? "" + : value instanceof Date ? formatDate(value) + : reFormat.test(value += "") ? "\"" + value.replace(/"/g, "\"\"") + "\"" + : value; + } + + return { + parse: parse, + parseRows: parseRows, + format: format, + formatBody: formatBody, + formatRows: formatRows, + formatRow: formatRow, + formatValue: formatValue + }; +} + +var csv$1 = dsvFormat(","); + +var csvParse = csv$1.parse; +var csvParseRows = csv$1.parseRows; +var csvFormat = csv$1.format; +var csvFormatBody = csv$1.formatBody; +var csvFormatRows = csv$1.formatRows; +var csvFormatRow = csv$1.formatRow; +var csvFormatValue = csv$1.formatValue; + +var tsv$1 = dsvFormat("\t"); + +var tsvParse = tsv$1.parse; +var tsvParseRows = tsv$1.parseRows; +var tsvFormat = tsv$1.format; +var tsvFormatBody = tsv$1.formatBody; +var tsvFormatRows = tsv$1.formatRows; +var tsvFormatRow = tsv$1.formatRow; +var tsvFormatValue = tsv$1.formatValue; + +function autoType(object) { + for (var key in object) { + var value = object[key].trim(), number, m; + if (!value) value = null; + else if (value === "true") value = true; + else if (value === "false") value = false; + else if (value === "NaN") value = NaN; + else if (!isNaN(number = +value)) value = number; + else if (m = value.match(/^([-+]\d{2})?\d{4}(-\d{2}(-\d{2})?)?(T\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/)) { + if (fixtz && !!m[4] && !m[7]) value = value.replace(/-/g, "/").replace(/T/, " "); + value = new Date(value); + } + else continue; + object[key] = value; + } + return object; +} + +// https://github.com/d3/d3-dsv/issues/45 +const fixtz = new Date("2019-01-01T00:00").getHours() || new Date("2019-07-01T00:00").getHours(); + +function responseBlob(response) { + if (!response.ok) throw new Error(response.status + " " + response.statusText); + return response.blob(); +} + +function blob(input, init) { + return fetch(input, init).then(responseBlob); +} + +function responseArrayBuffer(response) { + if (!response.ok) throw new Error(response.status + " " + response.statusText); + return response.arrayBuffer(); +} + +function buffer(input, init) { + return fetch(input, init).then(responseArrayBuffer); +} + +function responseText(response) { + if (!response.ok) throw new Error(response.status + " " + response.statusText); + return response.text(); +} + +function text(input, init) { + return fetch(input, init).then(responseText); +} + +function dsvParse(parse) { + return function(input, init, row) { + if (arguments.length === 2 && typeof init === "function") row = init, init = undefined; + return text(input, init).then(function(response) { + return parse(response, row); + }); + }; +} + +function dsv(delimiter, input, init, row) { + if (arguments.length === 3 && typeof init === "function") row = init, init = undefined; + var format = dsvFormat(delimiter); + return text(input, init).then(function(response) { + return format.parse(response, row); + }); +} + +var csv = dsvParse(csvParse); +var tsv = dsvParse(tsvParse); + +function image(input, init) { + return new Promise(function(resolve, reject) { + var image = new Image; + for (var key in init) image[key] = init[key]; + image.onerror = reject; + image.onload = function() { resolve(image); }; + image.src = input; + }); +} + +function responseJson(response) { + if (!response.ok) throw new Error(response.status + " " + response.statusText); + if (response.status === 204 || response.status === 205) return; + return response.json(); +} + +function json(input, init) { + return fetch(input, init).then(responseJson); +} + +function parser(type) { + return (input, init) => text(input, init) + .then(text => (new DOMParser).parseFromString(text, type)); +} + +var xml = parser("application/xml"); + +var html = parser("text/html"); + +var svg = parser("image/svg+xml"); + +function center(x, y) { + var nodes, strength = 1; + + if (x == null) x = 0; + if (y == null) y = 0; + + function force() { + var i, + n = nodes.length, + node, + sx = 0, + sy = 0; + + for (i = 0; i < n; ++i) { + node = nodes[i], sx += node.x, sy += node.y; + } + + for (sx = (sx / n - x) * strength, sy = (sy / n - y) * strength, i = 0; i < n; ++i) { + node = nodes[i], node.x -= sx, node.y -= sy; + } + } + + force.initialize = function(_) { + nodes = _; + }; + + force.x = function(_) { + return arguments.length ? (x = +_, force) : x; + }; + + force.y = function(_) { + return arguments.length ? (y = +_, force) : y; + }; + + force.strength = function(_) { + return arguments.length ? (strength = +_, force) : strength; + }; + + return force; +} + +function tree_add(d) { + const x = +this._x.call(null, d), + y = +this._y.call(null, d); + return add(this.cover(x, y), x, y, d); +} + +function add(tree, x, y, d) { + if (isNaN(x) || isNaN(y)) return tree; // ignore invalid points + + var parent, + node = tree._root, + leaf = {data: d}, + x0 = tree._x0, + y0 = tree._y0, + x1 = tree._x1, + y1 = tree._y1, + xm, + ym, + xp, + yp, + right, + bottom, + i, + j; + + // If the tree is empty, initialize the root as a leaf. + if (!node) return tree._root = leaf, tree; + + // Find the existing leaf for the new point, or add it. + while (node.length) { + if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm; + if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym; + if (parent = node, !(node = node[i = bottom << 1 | right])) return parent[i] = leaf, tree; + } + + // Is the new point is exactly coincident with the existing point? + xp = +tree._x.call(null, node.data); + yp = +tree._y.call(null, node.data); + if (x === xp && y === yp) return leaf.next = node, parent ? parent[i] = leaf : tree._root = leaf, tree; + + // Otherwise, split the leaf node until the old and new point are separated. + do { + parent = parent ? parent[i] = new Array(4) : tree._root = new Array(4); + if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm; + if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym; + } while ((i = bottom << 1 | right) === (j = (yp >= ym) << 1 | (xp >= xm))); + return parent[j] = node, parent[i] = leaf, tree; +} + +function addAll(data) { + var d, i, n = data.length, + x, + y, + xz = new Array(n), + yz = new Array(n), + x0 = Infinity, + y0 = Infinity, + x1 = -Infinity, + y1 = -Infinity; + + // Compute the points and their extent. + for (i = 0; i < n; ++i) { + if (isNaN(x = +this._x.call(null, d = data[i])) || isNaN(y = +this._y.call(null, d))) continue; + xz[i] = x; + yz[i] = y; + if (x < x0) x0 = x; + if (x > x1) x1 = x; + if (y < y0) y0 = y; + if (y > y1) y1 = y; + } + + // If there were no (valid) points, abort. + if (x0 > x1 || y0 > y1) return this; + + // Expand the tree to cover the new points. + this.cover(x0, y0).cover(x1, y1); + + // Add the new points. + for (i = 0; i < n; ++i) { + add(this, xz[i], yz[i], data[i]); + } + + return this; +} + +function tree_cover(x, y) { + if (isNaN(x = +x) || isNaN(y = +y)) return this; // ignore invalid points + + var x0 = this._x0, + y0 = this._y0, + x1 = this._x1, + y1 = this._y1; + + // If the quadtree has no extent, initialize them. + // Integer extent are necessary so that if we later double the extent, + // the existing quadrant boundaries don’t change due to floating point error! + if (isNaN(x0)) { + x1 = (x0 = Math.floor(x)) + 1; + y1 = (y0 = Math.floor(y)) + 1; + } + + // Otherwise, double repeatedly to cover. + else { + var z = x1 - x0 || 1, + node = this._root, + parent, + i; + + while (x0 > x || x >= x1 || y0 > y || y >= y1) { + i = (y < y0) << 1 | (x < x0); + parent = new Array(4), parent[i] = node, node = parent, z *= 2; + switch (i) { + case 0: x1 = x0 + z, y1 = y0 + z; break; + case 1: x0 = x1 - z, y1 = y0 + z; break; + case 2: x1 = x0 + z, y0 = y1 - z; break; + case 3: x0 = x1 - z, y0 = y1 - z; break; + } + } + + if (this._root && this._root.length) this._root = node; + } + + this._x0 = x0; + this._y0 = y0; + this._x1 = x1; + this._y1 = y1; + return this; +} + +function tree_data() { + var data = []; + this.visit(function(node) { + if (!node.length) do data.push(node.data); while (node = node.next) + }); + return data; +} + +function tree_extent(_) { + return arguments.length + ? this.cover(+_[0][0], +_[0][1]).cover(+_[1][0], +_[1][1]) + : isNaN(this._x0) ? undefined : [[this._x0, this._y0], [this._x1, this._y1]]; +} + +function Quad(node, x0, y0, x1, y1) { + this.node = node; + this.x0 = x0; + this.y0 = y0; + this.x1 = x1; + this.y1 = y1; +} + +function tree_find(x, y, radius) { + var data, + x0 = this._x0, + y0 = this._y0, + x1, + y1, + x2, + y2, + x3 = this._x1, + y3 = this._y1, + quads = [], + node = this._root, + q, + i; + + if (node) quads.push(new Quad(node, x0, y0, x3, y3)); + if (radius == null) radius = Infinity; + else { + x0 = x - radius, y0 = y - radius; + x3 = x + radius, y3 = y + radius; + radius *= radius; + } + + while (q = quads.pop()) { + + // Stop searching if this quadrant can’t contain a closer node. + if (!(node = q.node) + || (x1 = q.x0) > x3 + || (y1 = q.y0) > y3 + || (x2 = q.x1) < x0 + || (y2 = q.y1) < y0) continue; + + // Bisect the current quadrant. + if (node.length) { + var xm = (x1 + x2) / 2, + ym = (y1 + y2) / 2; + + quads.push( + new Quad(node[3], xm, ym, x2, y2), + new Quad(node[2], x1, ym, xm, y2), + new Quad(node[1], xm, y1, x2, ym), + new Quad(node[0], x1, y1, xm, ym) + ); + + // Visit the closest quadrant first. + if (i = (y >= ym) << 1 | (x >= xm)) { + q = quads[quads.length - 1]; + quads[quads.length - 1] = quads[quads.length - 1 - i]; + quads[quads.length - 1 - i] = q; + } + } + + // Visit this point. (Visiting coincident points isn’t necessary!) + else { + var dx = x - +this._x.call(null, node.data), + dy = y - +this._y.call(null, node.data), + d2 = dx * dx + dy * dy; + if (d2 < radius) { + var d = Math.sqrt(radius = d2); + x0 = x - d, y0 = y - d; + x3 = x + d, y3 = y + d; + data = node.data; + } + } + } + + return data; +} + +function tree_remove(d) { + if (isNaN(x = +this._x.call(null, d)) || isNaN(y = +this._y.call(null, d))) return this; // ignore invalid points + + var parent, + node = this._root, + retainer, + previous, + next, + x0 = this._x0, + y0 = this._y0, + x1 = this._x1, + y1 = this._y1, + x, + y, + xm, + ym, + right, + bottom, + i, + j; + + // If the tree is empty, initialize the root as a leaf. + if (!node) return this; + + // Find the leaf node for the point. + // While descending, also retain the deepest parent with a non-removed sibling. + if (node.length) while (true) { + if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm; + if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym; + if (!(parent = node, node = node[i = bottom << 1 | right])) return this; + if (!node.length) break; + if (parent[(i + 1) & 3] || parent[(i + 2) & 3] || parent[(i + 3) & 3]) retainer = parent, j = i; + } + + // Find the point to remove. + while (node.data !== d) if (!(previous = node, node = node.next)) return this; + if (next = node.next) delete node.next; + + // If there are multiple coincident points, remove just the point. + if (previous) return (next ? previous.next = next : delete previous.next), this; + + // If this is the root point, remove it. + if (!parent) return this._root = next, this; + + // Remove this leaf. + next ? parent[i] = next : delete parent[i]; + + // If the parent now contains exactly one leaf, collapse superfluous parents. + if ((node = parent[0] || parent[1] || parent[2] || parent[3]) + && node === (parent[3] || parent[2] || parent[1] || parent[0]) + && !node.length) { + if (retainer) retainer[j] = node; + else this._root = node; + } + + return this; +} + +function removeAll(data) { + for (var i = 0, n = data.length; i < n; ++i) this.remove(data[i]); + return this; +} + +function tree_root() { + return this._root; +} + +function tree_size() { + var size = 0; + this.visit(function(node) { + if (!node.length) do ++size; while (node = node.next) + }); + return size; +} + +function tree_visit(callback) { + var quads = [], q, node = this._root, child, x0, y0, x1, y1; + if (node) quads.push(new Quad(node, this._x0, this._y0, this._x1, this._y1)); + while (q = quads.pop()) { + if (!callback(node = q.node, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1) && node.length) { + var xm = (x0 + x1) / 2, ym = (y0 + y1) / 2; + if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1)); + if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1)); + if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym)); + if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym)); + } + } + return this; +} + +function tree_visitAfter(callback) { + var quads = [], next = [], q; + if (this._root) quads.push(new Quad(this._root, this._x0, this._y0, this._x1, this._y1)); + while (q = quads.pop()) { + var node = q.node; + if (node.length) { + var child, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1, xm = (x0 + x1) / 2, ym = (y0 + y1) / 2; + if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym)); + if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym)); + if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1)); + if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1)); + } + next.push(q); + } + while (q = next.pop()) { + callback(q.node, q.x0, q.y0, q.x1, q.y1); + } + return this; +} + +function defaultX(d) { + return d[0]; +} + +function tree_x(_) { + return arguments.length ? (this._x = _, this) : this._x; +} + +function defaultY(d) { + return d[1]; +} + +function tree_y(_) { + return arguments.length ? (this._y = _, this) : this._y; +} + +function quadtree(nodes, x, y) { + var tree = new Quadtree(x == null ? defaultX : x, y == null ? defaultY : y, NaN, NaN, NaN, NaN); + return nodes == null ? tree : tree.addAll(nodes); +} + +function Quadtree(x, y, x0, y0, x1, y1) { + this._x = x; + this._y = y; + this._x0 = x0; + this._y0 = y0; + this._x1 = x1; + this._y1 = y1; + this._root = undefined; +} + +function leaf_copy(leaf) { + var copy = {data: leaf.data}, next = copy; + while (leaf = leaf.next) next = next.next = {data: leaf.data}; + return copy; +} + +var treeProto = quadtree.prototype = Quadtree.prototype; + +treeProto.copy = function() { + var copy = new Quadtree(this._x, this._y, this._x0, this._y0, this._x1, this._y1), + node = this._root, + nodes, + child; + + if (!node) return copy; + + if (!node.length) return copy._root = leaf_copy(node), copy; + + nodes = [{source: node, target: copy._root = new Array(4)}]; + while (node = nodes.pop()) { + for (var i = 0; i < 4; ++i) { + if (child = node.source[i]) { + if (child.length) nodes.push({source: child, target: node.target[i] = new Array(4)}); + else node.target[i] = leaf_copy(child); + } + } + } + + return copy; +}; + +treeProto.add = tree_add; +treeProto.addAll = addAll; +treeProto.cover = tree_cover; +treeProto.data = tree_data; +treeProto.extent = tree_extent; +treeProto.find = tree_find; +treeProto.remove = tree_remove; +treeProto.removeAll = removeAll; +treeProto.root = tree_root; +treeProto.size = tree_size; +treeProto.visit = tree_visit; +treeProto.visitAfter = tree_visitAfter; +treeProto.x = tree_x; +treeProto.y = tree_y; + +function constant$4(x) { + return function() { + return x; + }; +} + +function jiggle(random) { + return (random() - 0.5) * 1e-6; +} + +function x$3(d) { + return d.x + d.vx; +} + +function y$3(d) { + return d.y + d.vy; +} + +function collide(radius) { + var nodes, + radii, + random, + strength = 1, + iterations = 1; + + if (typeof radius !== "function") radius = constant$4(radius == null ? 1 : +radius); + + function force() { + var i, n = nodes.length, + tree, + node, + xi, + yi, + ri, + ri2; + + for (var k = 0; k < iterations; ++k) { + tree = quadtree(nodes, x$3, y$3).visitAfter(prepare); + for (i = 0; i < n; ++i) { + node = nodes[i]; + ri = radii[node.index], ri2 = ri * ri; + xi = node.x + node.vx; + yi = node.y + node.vy; + tree.visit(apply); + } + } + + function apply(quad, x0, y0, x1, y1) { + var data = quad.data, rj = quad.r, r = ri + rj; + if (data) { + if (data.index > node.index) { + var x = xi - data.x - data.vx, + y = yi - data.y - data.vy, + l = x * x + y * y; + if (l < r * r) { + if (x === 0) x = jiggle(random), l += x * x; + if (y === 0) y = jiggle(random), l += y * y; + l = (r - (l = Math.sqrt(l))) / l * strength; + node.vx += (x *= l) * (r = (rj *= rj) / (ri2 + rj)); + node.vy += (y *= l) * r; + data.vx -= x * (r = 1 - r); + data.vy -= y * r; + } + } + return; + } + return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r; + } + } + + function prepare(quad) { + if (quad.data) return quad.r = radii[quad.data.index]; + for (var i = quad.r = 0; i < 4; ++i) { + if (quad[i] && quad[i].r > quad.r) { + quad.r = quad[i].r; + } + } + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length, node; + radii = new Array(n); + for (i = 0; i < n; ++i) node = nodes[i], radii[node.index] = +radius(node, i, nodes); + } + + force.initialize = function(_nodes, _random) { + nodes = _nodes; + random = _random; + initialize(); + }; + + force.iterations = function(_) { + return arguments.length ? (iterations = +_, force) : iterations; + }; + + force.strength = function(_) { + return arguments.length ? (strength = +_, force) : strength; + }; + + force.radius = function(_) { + return arguments.length ? (radius = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : radius; + }; + + return force; +} + +function index$3(d) { + return d.index; +} + +function find(nodeById, nodeId) { + var node = nodeById.get(nodeId); + if (!node) throw new Error("node not found: " + nodeId); + return node; +} + +function link$2(links) { + var id = index$3, + strength = defaultStrength, + strengths, + distance = constant$4(30), + distances, + nodes, + count, + bias, + random, + iterations = 1; + + if (links == null) links = []; + + function defaultStrength(link) { + return 1 / Math.min(count[link.source.index], count[link.target.index]); + } + + function force(alpha) { + for (var k = 0, n = links.length; k < iterations; ++k) { + for (var i = 0, link, source, target, x, y, l, b; i < n; ++i) { + link = links[i], source = link.source, target = link.target; + x = target.x + target.vx - source.x - source.vx || jiggle(random); + y = target.y + target.vy - source.y - source.vy || jiggle(random); + l = Math.sqrt(x * x + y * y); + l = (l - distances[i]) / l * alpha * strengths[i]; + x *= l, y *= l; + target.vx -= x * (b = bias[i]); + target.vy -= y * b; + source.vx += x * (b = 1 - b); + source.vy += y * b; + } + } + } + + function initialize() { + if (!nodes) return; + + var i, + n = nodes.length, + m = links.length, + nodeById = new Map(nodes.map((d, i) => [id(d, i, nodes), d])), + link; + + for (i = 0, count = new Array(n); i < m; ++i) { + link = links[i], link.index = i; + if (typeof link.source !== "object") link.source = find(nodeById, link.source); + if (typeof link.target !== "object") link.target = find(nodeById, link.target); + count[link.source.index] = (count[link.source.index] || 0) + 1; + count[link.target.index] = (count[link.target.index] || 0) + 1; + } + + for (i = 0, bias = new Array(m); i < m; ++i) { + link = links[i], bias[i] = count[link.source.index] / (count[link.source.index] + count[link.target.index]); + } + + strengths = new Array(m), initializeStrength(); + distances = new Array(m), initializeDistance(); + } + + function initializeStrength() { + if (!nodes) return; + + for (var i = 0, n = links.length; i < n; ++i) { + strengths[i] = +strength(links[i], i, links); + } + } + + function initializeDistance() { + if (!nodes) return; + + for (var i = 0, n = links.length; i < n; ++i) { + distances[i] = +distance(links[i], i, links); + } + } + + force.initialize = function(_nodes, _random) { + nodes = _nodes; + random = _random; + initialize(); + }; + + force.links = function(_) { + return arguments.length ? (links = _, initialize(), force) : links; + }; + + force.id = function(_) { + return arguments.length ? (id = _, force) : id; + }; + + force.iterations = function(_) { + return arguments.length ? (iterations = +_, force) : iterations; + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initializeStrength(), force) : strength; + }; + + force.distance = function(_) { + return arguments.length ? (distance = typeof _ === "function" ? _ : constant$4(+_), initializeDistance(), force) : distance; + }; + + return force; +} + +// https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use +const a$2 = 1664525; +const c$4 = 1013904223; +const m$1 = 4294967296; // 2^32 + +function lcg$2() { + let s = 1; + return () => (s = (a$2 * s + c$4) % m$1) / m$1; +} + +function x$2(d) { + return d.x; +} + +function y$2(d) { + return d.y; +} + +var initialRadius = 10, + initialAngle = Math.PI * (3 - Math.sqrt(5)); + +function simulation(nodes) { + var simulation, + alpha = 1, + alphaMin = 0.001, + alphaDecay = 1 - Math.pow(alphaMin, 1 / 300), + alphaTarget = 0, + velocityDecay = 0.6, + forces = new Map(), + stepper = timer(step), + event = dispatch("tick", "end"), + random = lcg$2(); + + if (nodes == null) nodes = []; + + function step() { + tick(); + event.call("tick", simulation); + if (alpha < alphaMin) { + stepper.stop(); + event.call("end", simulation); + } + } + + function tick(iterations) { + var i, n = nodes.length, node; + + if (iterations === undefined) iterations = 1; + + for (var k = 0; k < iterations; ++k) { + alpha += (alphaTarget - alpha) * alphaDecay; + + forces.forEach(function(force) { + force(alpha); + }); + + for (i = 0; i < n; ++i) { + node = nodes[i]; + if (node.fx == null) node.x += node.vx *= velocityDecay; + else node.x = node.fx, node.vx = 0; + if (node.fy == null) node.y += node.vy *= velocityDecay; + else node.y = node.fy, node.vy = 0; + } + } + + return simulation; + } + + function initializeNodes() { + for (var i = 0, n = nodes.length, node; i < n; ++i) { + node = nodes[i], node.index = i; + if (node.fx != null) node.x = node.fx; + if (node.fy != null) node.y = node.fy; + if (isNaN(node.x) || isNaN(node.y)) { + var radius = initialRadius * Math.sqrt(0.5 + i), angle = i * initialAngle; + node.x = radius * Math.cos(angle); + node.y = radius * Math.sin(angle); + } + if (isNaN(node.vx) || isNaN(node.vy)) { + node.vx = node.vy = 0; + } + } + } + + function initializeForce(force) { + if (force.initialize) force.initialize(nodes, random); + return force; + } + + initializeNodes(); + + return simulation = { + tick: tick, + + restart: function() { + return stepper.restart(step), simulation; + }, + + stop: function() { + return stepper.stop(), simulation; + }, + + nodes: function(_) { + return arguments.length ? (nodes = _, initializeNodes(), forces.forEach(initializeForce), simulation) : nodes; + }, + + alpha: function(_) { + return arguments.length ? (alpha = +_, simulation) : alpha; + }, + + alphaMin: function(_) { + return arguments.length ? (alphaMin = +_, simulation) : alphaMin; + }, + + alphaDecay: function(_) { + return arguments.length ? (alphaDecay = +_, simulation) : +alphaDecay; + }, + + alphaTarget: function(_) { + return arguments.length ? (alphaTarget = +_, simulation) : alphaTarget; + }, + + velocityDecay: function(_) { + return arguments.length ? (velocityDecay = 1 - _, simulation) : 1 - velocityDecay; + }, + + randomSource: function(_) { + return arguments.length ? (random = _, forces.forEach(initializeForce), simulation) : random; + }, + + force: function(name, _) { + return arguments.length > 1 ? ((_ == null ? forces.delete(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name); + }, + + find: function(x, y, radius) { + var i = 0, + n = nodes.length, + dx, + dy, + d2, + node, + closest; + + if (radius == null) radius = Infinity; + else radius *= radius; + + for (i = 0; i < n; ++i) { + node = nodes[i]; + dx = x - node.x; + dy = y - node.y; + d2 = dx * dx + dy * dy; + if (d2 < radius) closest = node, radius = d2; + } + + return closest; + }, + + on: function(name, _) { + return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name); + } + }; +} + +function manyBody() { + var nodes, + node, + random, + alpha, + strength = constant$4(-30), + strengths, + distanceMin2 = 1, + distanceMax2 = Infinity, + theta2 = 0.81; + + function force(_) { + var i, n = nodes.length, tree = quadtree(nodes, x$2, y$2).visitAfter(accumulate); + for (alpha = _, i = 0; i < n; ++i) node = nodes[i], tree.visit(apply); + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length, node; + strengths = new Array(n); + for (i = 0; i < n; ++i) node = nodes[i], strengths[node.index] = +strength(node, i, nodes); + } + + function accumulate(quad) { + var strength = 0, q, c, weight = 0, x, y, i; + + // For internal nodes, accumulate forces from child quadrants. + if (quad.length) { + for (x = y = i = 0; i < 4; ++i) { + if ((q = quad[i]) && (c = Math.abs(q.value))) { + strength += q.value, weight += c, x += c * q.x, y += c * q.y; + } + } + quad.x = x / weight; + quad.y = y / weight; + } + + // For leaf nodes, accumulate forces from coincident quadrants. + else { + q = quad; + q.x = q.data.x; + q.y = q.data.y; + do strength += strengths[q.data.index]; + while (q = q.next); + } + + quad.value = strength; + } + + function apply(quad, x1, _, x2) { + if (!quad.value) return true; + + var x = quad.x - node.x, + y = quad.y - node.y, + w = x2 - x1, + l = x * x + y * y; + + // Apply the Barnes-Hut approximation if possible. + // Limit forces for very close nodes; randomize direction if coincident. + if (w * w / theta2 < l) { + if (l < distanceMax2) { + if (x === 0) x = jiggle(random), l += x * x; + if (y === 0) y = jiggle(random), l += y * y; + if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); + node.vx += x * quad.value * alpha / l; + node.vy += y * quad.value * alpha / l; + } + return true; + } + + // Otherwise, process points directly. + else if (quad.length || l >= distanceMax2) return; + + // Limit forces for very close nodes; randomize direction if coincident. + if (quad.data !== node || quad.next) { + if (x === 0) x = jiggle(random), l += x * x; + if (y === 0) y = jiggle(random), l += y * y; + if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); + } + + do if (quad.data !== node) { + w = strengths[quad.data.index] * alpha / l; + node.vx += x * w; + node.vy += y * w; + } while (quad = quad.next); + } + + force.initialize = function(_nodes, _random) { + nodes = _nodes; + random = _random; + initialize(); + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; + }; + + force.distanceMin = function(_) { + return arguments.length ? (distanceMin2 = _ * _, force) : Math.sqrt(distanceMin2); + }; + + force.distanceMax = function(_) { + return arguments.length ? (distanceMax2 = _ * _, force) : Math.sqrt(distanceMax2); + }; + + force.theta = function(_) { + return arguments.length ? (theta2 = _ * _, force) : Math.sqrt(theta2); + }; + + return force; +} + +function radial$1(radius, x, y) { + var nodes, + strength = constant$4(0.1), + strengths, + radiuses; + + if (typeof radius !== "function") radius = constant$4(+radius); + if (x == null) x = 0; + if (y == null) y = 0; + + function force(alpha) { + for (var i = 0, n = nodes.length; i < n; ++i) { + var node = nodes[i], + dx = node.x - x || 1e-6, + dy = node.y - y || 1e-6, + r = Math.sqrt(dx * dx + dy * dy), + k = (radiuses[i] - r) * strengths[i] * alpha / r; + node.vx += dx * k; + node.vy += dy * k; + } + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length; + strengths = new Array(n); + radiuses = new Array(n); + for (i = 0; i < n; ++i) { + radiuses[i] = +radius(nodes[i], i, nodes); + strengths[i] = isNaN(radiuses[i]) ? 0 : +strength(nodes[i], i, nodes); + } + } + + force.initialize = function(_) { + nodes = _, initialize(); + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; + }; + + force.radius = function(_) { + return arguments.length ? (radius = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : radius; + }; + + force.x = function(_) { + return arguments.length ? (x = +_, force) : x; + }; + + force.y = function(_) { + return arguments.length ? (y = +_, force) : y; + }; + + return force; +} + +function x$1(x) { + var strength = constant$4(0.1), + nodes, + strengths, + xz; + + if (typeof x !== "function") x = constant$4(x == null ? 0 : +x); + + function force(alpha) { + for (var i = 0, n = nodes.length, node; i < n; ++i) { + node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha; + } + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length; + strengths = new Array(n); + xz = new Array(n); + for (i = 0; i < n; ++i) { + strengths[i] = isNaN(xz[i] = +x(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes); + } + } + + force.initialize = function(_) { + nodes = _; + initialize(); + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; + }; + + force.x = function(_) { + return arguments.length ? (x = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : x; + }; + + return force; +} + +function y$1(y) { + var strength = constant$4(0.1), + nodes, + strengths, + yz; + + if (typeof y !== "function") y = constant$4(y == null ? 0 : +y); + + function force(alpha) { + for (var i = 0, n = nodes.length, node; i < n; ++i) { + node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha; + } + } + + function initialize() { + if (!nodes) return; + var i, n = nodes.length; + strengths = new Array(n); + yz = new Array(n); + for (i = 0; i < n; ++i) { + strengths[i] = isNaN(yz[i] = +y(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes); + } + } + + force.initialize = function(_) { + nodes = _; + initialize(); + }; + + force.strength = function(_) { + return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; + }; + + force.y = function(_) { + return arguments.length ? (y = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : y; + }; + + return force; +} + +function formatDecimal(x) { + return Math.abs(x = Math.round(x)) >= 1e21 + ? x.toLocaleString("en").replace(/,/g, "") + : x.toString(10); +} + +// Computes the decimal coefficient and exponent of the specified number x with +// significant digits p, where x is positive and p is in [1, 21] or undefined. +// For example, formatDecimalParts(1.23) returns ["123", 0]. +function formatDecimalParts(x, p) { + if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity + var i, coefficient = x.slice(0, i); + + // The string returned by toExponential either has the form \d\.\d+e[-+]\d+ + // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3). + return [ + coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, + +x.slice(i + 1) + ]; +} + +function exponent(x) { + return x = formatDecimalParts(Math.abs(x)), x ? x[1] : NaN; +} + +function formatGroup(grouping, thousands) { + return function(value, width) { + var i = value.length, + t = [], + j = 0, + g = grouping[0], + length = 0; + + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = grouping[j = (j + 1) % grouping.length]; + } + + return t.reverse().join(thousands); + }; +} + +function formatNumerals(numerals) { + return function(value) { + return value.replace(/[0-9]/g, function(i) { + return numerals[+i]; + }); + }; +} + +// [[fill]align][sign][symbol][0][width][,][.precision][~][type] +var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i; + +function formatSpecifier(specifier) { + if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); + var match; + return new FormatSpecifier({ + fill: match[1], + align: match[2], + sign: match[3], + symbol: match[4], + zero: match[5], + width: match[6], + comma: match[7], + precision: match[8] && match[8].slice(1), + trim: match[9], + type: match[10] + }); +} + +formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof + +function FormatSpecifier(specifier) { + this.fill = specifier.fill === undefined ? " " : specifier.fill + ""; + this.align = specifier.align === undefined ? ">" : specifier.align + ""; + this.sign = specifier.sign === undefined ? "-" : specifier.sign + ""; + this.symbol = specifier.symbol === undefined ? "" : specifier.symbol + ""; + this.zero = !!specifier.zero; + this.width = specifier.width === undefined ? undefined : +specifier.width; + this.comma = !!specifier.comma; + this.precision = specifier.precision === undefined ? undefined : +specifier.precision; + this.trim = !!specifier.trim; + this.type = specifier.type === undefined ? "" : specifier.type + ""; +} + +FormatSpecifier.prototype.toString = function() { + return this.fill + + this.align + + this.sign + + this.symbol + + (this.zero ? "0" : "") + + (this.width === undefined ? "" : Math.max(1, this.width | 0)) + + (this.comma ? "," : "") + + (this.precision === undefined ? "" : "." + Math.max(0, this.precision | 0)) + + (this.trim ? "~" : "") + + this.type; +}; + +// Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k. +function formatTrim(s) { + out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) { + switch (s[i]) { + case ".": i0 = i1 = i; break; + case "0": if (i0 === 0) i0 = i; i1 = i; break; + default: if (!+s[i]) break out; if (i0 > 0) i0 = 0; break; + } + } + return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s; +} + +var prefixExponent; + +function formatPrefixAuto(x, p) { + var d = formatDecimalParts(x, p); + if (!d) return x + ""; + var coefficient = d[0], + exponent = d[1], + i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, + n = coefficient.length; + return i === n ? coefficient + : i > n ? coefficient + new Array(i - n + 1).join("0") + : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) + : "0." + new Array(1 - i).join("0") + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y! +} + +function formatRounded(x, p) { + var d = formatDecimalParts(x, p); + if (!d) return x + ""; + var coefficient = d[0], + exponent = d[1]; + return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient + : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) + : coefficient + new Array(exponent - coefficient.length + 2).join("0"); +} + +var formatTypes = { + "%": (x, p) => (x * 100).toFixed(p), + "b": (x) => Math.round(x).toString(2), + "c": (x) => x + "", + "d": formatDecimal, + "e": (x, p) => x.toExponential(p), + "f": (x, p) => x.toFixed(p), + "g": (x, p) => x.toPrecision(p), + "o": (x) => Math.round(x).toString(8), + "p": (x, p) => formatRounded(x * 100, p), + "r": formatRounded, + "s": formatPrefixAuto, + "X": (x) => Math.round(x).toString(16).toUpperCase(), + "x": (x) => Math.round(x).toString(16) +}; + +function identity$6(x) { + return x; +} + +var map = Array.prototype.map, + prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"]; + +function formatLocale$1(locale) { + var group = locale.grouping === undefined || locale.thousands === undefined ? identity$6 : formatGroup(map.call(locale.grouping, Number), locale.thousands + ""), + currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "", + currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "", + decimal = locale.decimal === undefined ? "." : locale.decimal + "", + numerals = locale.numerals === undefined ? identity$6 : formatNumerals(map.call(locale.numerals, String)), + percent = locale.percent === undefined ? "%" : locale.percent + "", + minus = locale.minus === undefined ? "−" : locale.minus + "", + nan = locale.nan === undefined ? "NaN" : locale.nan + ""; + + function newFormat(specifier) { + specifier = formatSpecifier(specifier); + + var fill = specifier.fill, + align = specifier.align, + sign = specifier.sign, + symbol = specifier.symbol, + zero = specifier.zero, + width = specifier.width, + comma = specifier.comma, + precision = specifier.precision, + trim = specifier.trim, + type = specifier.type; + + // The "n" type is an alias for ",g". + if (type === "n") comma = true, type = "g"; + + // The "" type, and any invalid type, is an alias for ".12~g". + else if (!formatTypes[type]) precision === undefined && (precision = 12), trim = true, type = "g"; + + // If zero fill is specified, padding goes after sign and before digits. + if (zero || (fill === "0" && align === "=")) zero = true, fill = "0", align = "="; + + // Compute the prefix and suffix. + // For SI-prefix, the suffix is lazily computed. + var prefix = symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "", + suffix = symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : ""; + + // What format function should we use? + // Is this an integer type? + // Can this type generate exponential notation? + var formatType = formatTypes[type], + maybeSuffix = /[defgprs%]/.test(type); + + // Set the default precision if not specified, + // or clamp the specified precision to the supported range. + // For significant precision, it must be in [1, 21]. + // For fixed precision, it must be in [0, 20]. + precision = precision === undefined ? 6 + : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) + : Math.max(0, Math.min(20, precision)); + + function format(value) { + var valuePrefix = prefix, + valueSuffix = suffix, + i, n, c; + + if (type === "c") { + valueSuffix = formatType(value) + valueSuffix; + value = ""; + } else { + value = +value; + + // Determine the sign. -0 is not less than 0, but 1 / -0 is! + var valueNegative = value < 0 || 1 / value < 0; + + // Perform the initial formatting. + value = isNaN(value) ? nan : formatType(Math.abs(value), precision); + + // Trim insignificant zeros. + if (trim) value = formatTrim(value); + + // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign. + if (valueNegative && +value === 0 && sign !== "+") valueNegative = false; + + // Compute the prefix and suffix. + valuePrefix = (valueNegative ? (sign === "(" ? sign : minus) : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; + valueSuffix = (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); + + // Break the formatted value into the integer “value” part that can be + // grouped, and fractional or exponential “suffix” part that is not. + if (maybeSuffix) { + i = -1, n = value.length; + while (++i < n) { + if (c = value.charCodeAt(i), 48 > c || c > 57) { + valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; + value = value.slice(0, i); + break; + } + } + } + } + + // If the fill character is not "0", grouping is applied before padding. + if (comma && !zero) value = group(value, Infinity); + + // Compute the padding. + var length = valuePrefix.length + value.length + valueSuffix.length, + padding = length < width ? new Array(width - length + 1).join(fill) : ""; + + // If the fill character is "0", grouping is applied after padding. + if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; + + // Reconstruct the final output based on the desired alignment. + switch (align) { + case "<": value = valuePrefix + value + valueSuffix + padding; break; + case "=": value = valuePrefix + padding + value + valueSuffix; break; + case "^": value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); break; + default: value = padding + valuePrefix + value + valueSuffix; break; + } + + return numerals(value); + } + + format.toString = function() { + return specifier + ""; + }; + + return format; + } + + function formatPrefix(specifier, value) { + var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)), + e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, + k = Math.pow(10, -e), + prefix = prefixes[8 + e / 3]; + return function(value) { + return f(k * value) + prefix; + }; + } + + return { + format: newFormat, + formatPrefix: formatPrefix + }; +} + +var locale$1; +exports.format = void 0; +exports.formatPrefix = void 0; + +defaultLocale$1({ + thousands: ",", + grouping: [3], + currency: ["$", ""] +}); + +function defaultLocale$1(definition) { + locale$1 = formatLocale$1(definition); + exports.format = locale$1.format; + exports.formatPrefix = locale$1.formatPrefix; + return locale$1; +} + +function precisionFixed(step) { + return Math.max(0, -exponent(Math.abs(step))); +} + +function precisionPrefix(step, value) { + return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step))); +} + +function precisionRound(step, max) { + step = Math.abs(step), max = Math.abs(max) - step; + return Math.max(0, exponent(max) - exponent(step)) + 1; +} + +var epsilon$1 = 1e-6; +var epsilon2 = 1e-12; +var pi$1 = Math.PI; +var halfPi$1 = pi$1 / 2; +var quarterPi = pi$1 / 4; +var tau$1 = pi$1 * 2; + +var degrees = 180 / pi$1; +var radians = pi$1 / 180; + +var abs$1 = Math.abs; +var atan = Math.atan; +var atan2$1 = Math.atan2; +var cos$1 = Math.cos; +var ceil = Math.ceil; +var exp = Math.exp; +var hypot = Math.hypot; +var log$1 = Math.log; +var pow$1 = Math.pow; +var sin$1 = Math.sin; +var sign$1 = Math.sign || function(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }; +var sqrt$2 = Math.sqrt; +var tan = Math.tan; + +function acos$1(x) { + return x > 1 ? 0 : x < -1 ? pi$1 : Math.acos(x); +} + +function asin$1(x) { + return x > 1 ? halfPi$1 : x < -1 ? -halfPi$1 : Math.asin(x); +} + +function haversin(x) { + return (x = sin$1(x / 2)) * x; +} + +function noop$1() {} + +function streamGeometry(geometry, stream) { + if (geometry && streamGeometryType.hasOwnProperty(geometry.type)) { + streamGeometryType[geometry.type](geometry, stream); + } +} + +var streamObjectType = { + Feature: function(object, stream) { + streamGeometry(object.geometry, stream); + }, + FeatureCollection: function(object, stream) { + var features = object.features, i = -1, n = features.length; + while (++i < n) streamGeometry(features[i].geometry, stream); + } +}; + +var streamGeometryType = { + Sphere: function(object, stream) { + stream.sphere(); + }, + Point: function(object, stream) { + object = object.coordinates; + stream.point(object[0], object[1], object[2]); + }, + MultiPoint: function(object, stream) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) object = coordinates[i], stream.point(object[0], object[1], object[2]); + }, + LineString: function(object, stream) { + streamLine(object.coordinates, stream, 0); + }, + MultiLineString: function(object, stream) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) streamLine(coordinates[i], stream, 0); + }, + Polygon: function(object, stream) { + streamPolygon(object.coordinates, stream); + }, + MultiPolygon: function(object, stream) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) streamPolygon(coordinates[i], stream); + }, + GeometryCollection: function(object, stream) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) streamGeometry(geometries[i], stream); + } +}; + +function streamLine(coordinates, stream, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + stream.lineStart(); + while (++i < n) coordinate = coordinates[i], stream.point(coordinate[0], coordinate[1], coordinate[2]); + stream.lineEnd(); +} + +function streamPolygon(coordinates, stream) { + var i = -1, n = coordinates.length; + stream.polygonStart(); + while (++i < n) streamLine(coordinates[i], stream, 1); + stream.polygonEnd(); +} + +function geoStream(object, stream) { + if (object && streamObjectType.hasOwnProperty(object.type)) { + streamObjectType[object.type](object, stream); + } else { + streamGeometry(object, stream); + } +} + +var areaRingSum$1 = new Adder(); + +// hello? + +var areaSum$1 = new Adder(), + lambda00$2, + phi00$2, + lambda0$2, + cosPhi0$1, + sinPhi0$1; + +var areaStream$1 = { + point: noop$1, + lineStart: noop$1, + lineEnd: noop$1, + polygonStart: function() { + areaRingSum$1 = new Adder(); + areaStream$1.lineStart = areaRingStart$1; + areaStream$1.lineEnd = areaRingEnd$1; + }, + polygonEnd: function() { + var areaRing = +areaRingSum$1; + areaSum$1.add(areaRing < 0 ? tau$1 + areaRing : areaRing); + this.lineStart = this.lineEnd = this.point = noop$1; + }, + sphere: function() { + areaSum$1.add(tau$1); + } +}; + +function areaRingStart$1() { + areaStream$1.point = areaPointFirst$1; +} + +function areaRingEnd$1() { + areaPoint$1(lambda00$2, phi00$2); +} + +function areaPointFirst$1(lambda, phi) { + areaStream$1.point = areaPoint$1; + lambda00$2 = lambda, phi00$2 = phi; + lambda *= radians, phi *= radians; + lambda0$2 = lambda, cosPhi0$1 = cos$1(phi = phi / 2 + quarterPi), sinPhi0$1 = sin$1(phi); +} + +function areaPoint$1(lambda, phi) { + lambda *= radians, phi *= radians; + phi = phi / 2 + quarterPi; // half the angular distance from south pole + + // Spherical excess E for a spherical triangle with vertices: south pole, + // previous point, current point. Uses a formula derived from Cagnoli’s + // theorem. See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2). + var dLambda = lambda - lambda0$2, + sdLambda = dLambda >= 0 ? 1 : -1, + adLambda = sdLambda * dLambda, + cosPhi = cos$1(phi), + sinPhi = sin$1(phi), + k = sinPhi0$1 * sinPhi, + u = cosPhi0$1 * cosPhi + k * cos$1(adLambda), + v = k * sdLambda * sin$1(adLambda); + areaRingSum$1.add(atan2$1(v, u)); + + // Advance the previous points. + lambda0$2 = lambda, cosPhi0$1 = cosPhi, sinPhi0$1 = sinPhi; +} + +function area$2(object) { + areaSum$1 = new Adder(); + geoStream(object, areaStream$1); + return areaSum$1 * 2; +} + +function spherical(cartesian) { + return [atan2$1(cartesian[1], cartesian[0]), asin$1(cartesian[2])]; +} + +function cartesian(spherical) { + var lambda = spherical[0], phi = spherical[1], cosPhi = cos$1(phi); + return [cosPhi * cos$1(lambda), cosPhi * sin$1(lambda), sin$1(phi)]; +} + +function cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +function cartesianCross(a, b) { + return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +} + +// TODO return a +function cartesianAddInPlace(a, b) { + a[0] += b[0], a[1] += b[1], a[2] += b[2]; +} + +function cartesianScale(vector, k) { + return [vector[0] * k, vector[1] * k, vector[2] * k]; +} + +// TODO return d +function cartesianNormalizeInPlace(d) { + var l = sqrt$2(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l, d[1] /= l, d[2] /= l; +} + +var lambda0$1, phi0, lambda1, phi1, // bounds + lambda2, // previous lambda-coordinate + lambda00$1, phi00$1, // first point + p0, // previous 3D point + deltaSum, + ranges, + range; + +var boundsStream$2 = { + point: boundsPoint$1, + lineStart: boundsLineStart, + lineEnd: boundsLineEnd, + polygonStart: function() { + boundsStream$2.point = boundsRingPoint; + boundsStream$2.lineStart = boundsRingStart; + boundsStream$2.lineEnd = boundsRingEnd; + deltaSum = new Adder(); + areaStream$1.polygonStart(); + }, + polygonEnd: function() { + areaStream$1.polygonEnd(); + boundsStream$2.point = boundsPoint$1; + boundsStream$2.lineStart = boundsLineStart; + boundsStream$2.lineEnd = boundsLineEnd; + if (areaRingSum$1 < 0) lambda0$1 = -(lambda1 = 180), phi0 = -(phi1 = 90); + else if (deltaSum > epsilon$1) phi1 = 90; + else if (deltaSum < -epsilon$1) phi0 = -90; + range[0] = lambda0$1, range[1] = lambda1; + }, + sphere: function() { + lambda0$1 = -(lambda1 = 180), phi0 = -(phi1 = 90); + } +}; + +function boundsPoint$1(lambda, phi) { + ranges.push(range = [lambda0$1 = lambda, lambda1 = lambda]); + if (phi < phi0) phi0 = phi; + if (phi > phi1) phi1 = phi; +} + +function linePoint(lambda, phi) { + var p = cartesian([lambda * radians, phi * radians]); + if (p0) { + var normal = cartesianCross(p0, p), + equatorial = [normal[1], -normal[0], 0], + inflection = cartesianCross(equatorial, normal); + cartesianNormalizeInPlace(inflection); + inflection = spherical(inflection); + var delta = lambda - lambda2, + sign = delta > 0 ? 1 : -1, + lambdai = inflection[0] * degrees * sign, + phii, + antimeridian = abs$1(delta) > 180; + if (antimeridian ^ (sign * lambda2 < lambdai && lambdai < sign * lambda)) { + phii = inflection[1] * degrees; + if (phii > phi1) phi1 = phii; + } else if (lambdai = (lambdai + 360) % 360 - 180, antimeridian ^ (sign * lambda2 < lambdai && lambdai < sign * lambda)) { + phii = -inflection[1] * degrees; + if (phii < phi0) phi0 = phii; + } else { + if (phi < phi0) phi0 = phi; + if (phi > phi1) phi1 = phi; + } + if (antimeridian) { + if (lambda < lambda2) { + if (angle(lambda0$1, lambda) > angle(lambda0$1, lambda1)) lambda1 = lambda; + } else { + if (angle(lambda, lambda1) > angle(lambda0$1, lambda1)) lambda0$1 = lambda; + } + } else { + if (lambda1 >= lambda0$1) { + if (lambda < lambda0$1) lambda0$1 = lambda; + if (lambda > lambda1) lambda1 = lambda; + } else { + if (lambda > lambda2) { + if (angle(lambda0$1, lambda) > angle(lambda0$1, lambda1)) lambda1 = lambda; + } else { + if (angle(lambda, lambda1) > angle(lambda0$1, lambda1)) lambda0$1 = lambda; + } + } + } + } else { + ranges.push(range = [lambda0$1 = lambda, lambda1 = lambda]); + } + if (phi < phi0) phi0 = phi; + if (phi > phi1) phi1 = phi; + p0 = p, lambda2 = lambda; +} + +function boundsLineStart() { + boundsStream$2.point = linePoint; +} + +function boundsLineEnd() { + range[0] = lambda0$1, range[1] = lambda1; + boundsStream$2.point = boundsPoint$1; + p0 = null; +} + +function boundsRingPoint(lambda, phi) { + if (p0) { + var delta = lambda - lambda2; + deltaSum.add(abs$1(delta) > 180 ? delta + (delta > 0 ? 360 : -360) : delta); + } else { + lambda00$1 = lambda, phi00$1 = phi; + } + areaStream$1.point(lambda, phi); + linePoint(lambda, phi); +} + +function boundsRingStart() { + areaStream$1.lineStart(); +} + +function boundsRingEnd() { + boundsRingPoint(lambda00$1, phi00$1); + areaStream$1.lineEnd(); + if (abs$1(deltaSum) > epsilon$1) lambda0$1 = -(lambda1 = 180); + range[0] = lambda0$1, range[1] = lambda1; + p0 = null; +} + +// Finds the left-right distance between two longitudes. +// This is almost the same as (lambda1 - lambda0 + 360°) % 360°, except that we want +// the distance between ±180° to be 360°. +function angle(lambda0, lambda1) { + return (lambda1 -= lambda0) < 0 ? lambda1 + 360 : lambda1; +} + +function rangeCompare(a, b) { + return a[0] - b[0]; +} + +function rangeContains(range, x) { + return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; +} + +function bounds(feature) { + var i, n, a, b, merged, deltaMax, delta; + + phi1 = lambda1 = -(lambda0$1 = phi0 = Infinity); + ranges = []; + geoStream(feature, boundsStream$2); + + // First, sort ranges by their minimum longitudes. + if (n = ranges.length) { + ranges.sort(rangeCompare); + + // Then, merge any ranges that overlap. + for (i = 1, a = ranges[0], merged = [a]; i < n; ++i) { + b = ranges[i]; + if (rangeContains(a, b[0]) || rangeContains(a, b[1])) { + if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1]; + if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0]; + } else { + merged.push(a = b); + } + } + + // Finally, find the largest gap between the merged ranges. + // The final bounding box will be the inverse of this gap. + for (deltaMax = -Infinity, n = merged.length - 1, i = 0, a = merged[n]; i <= n; a = b, ++i) { + b = merged[i]; + if ((delta = angle(a[1], b[0])) > deltaMax) deltaMax = delta, lambda0$1 = b[0], lambda1 = a[1]; + } + } + + ranges = range = null; + + return lambda0$1 === Infinity || phi0 === Infinity + ? [[NaN, NaN], [NaN, NaN]] + : [[lambda0$1, phi0], [lambda1, phi1]]; +} + +var W0, W1, + X0$1, Y0$1, Z0$1, + X1$1, Y1$1, Z1$1, + X2$1, Y2$1, Z2$1, + lambda00, phi00, // first point + x0$4, y0$4, z0; // previous point + +var centroidStream$1 = { + sphere: noop$1, + point: centroidPoint$1, + lineStart: centroidLineStart$1, + lineEnd: centroidLineEnd$1, + polygonStart: function() { + centroidStream$1.lineStart = centroidRingStart$1; + centroidStream$1.lineEnd = centroidRingEnd$1; + }, + polygonEnd: function() { + centroidStream$1.lineStart = centroidLineStart$1; + centroidStream$1.lineEnd = centroidLineEnd$1; + } +}; + +// Arithmetic mean of Cartesian vectors. +function centroidPoint$1(lambda, phi) { + lambda *= radians, phi *= radians; + var cosPhi = cos$1(phi); + centroidPointCartesian(cosPhi * cos$1(lambda), cosPhi * sin$1(lambda), sin$1(phi)); +} + +function centroidPointCartesian(x, y, z) { + ++W0; + X0$1 += (x - X0$1) / W0; + Y0$1 += (y - Y0$1) / W0; + Z0$1 += (z - Z0$1) / W0; +} + +function centroidLineStart$1() { + centroidStream$1.point = centroidLinePointFirst; +} + +function centroidLinePointFirst(lambda, phi) { + lambda *= radians, phi *= radians; + var cosPhi = cos$1(phi); + x0$4 = cosPhi * cos$1(lambda); + y0$4 = cosPhi * sin$1(lambda); + z0 = sin$1(phi); + centroidStream$1.point = centroidLinePoint; + centroidPointCartesian(x0$4, y0$4, z0); +} + +function centroidLinePoint(lambda, phi) { + lambda *= radians, phi *= radians; + var cosPhi = cos$1(phi), + x = cosPhi * cos$1(lambda), + y = cosPhi * sin$1(lambda), + z = sin$1(phi), + w = atan2$1(sqrt$2((w = y0$4 * z - z0 * y) * w + (w = z0 * x - x0$4 * z) * w + (w = x0$4 * y - y0$4 * x) * w), x0$4 * x + y0$4 * y + z0 * z); + W1 += w; + X1$1 += w * (x0$4 + (x0$4 = x)); + Y1$1 += w * (y0$4 + (y0$4 = y)); + Z1$1 += w * (z0 + (z0 = z)); + centroidPointCartesian(x0$4, y0$4, z0); +} + +function centroidLineEnd$1() { + centroidStream$1.point = centroidPoint$1; +} + +// See J. E. Brock, The Inertia Tensor for a Spherical Triangle, +// J. Applied Mechanics 42, 239 (1975). +function centroidRingStart$1() { + centroidStream$1.point = centroidRingPointFirst; +} + +function centroidRingEnd$1() { + centroidRingPoint(lambda00, phi00); + centroidStream$1.point = centroidPoint$1; +} + +function centroidRingPointFirst(lambda, phi) { + lambda00 = lambda, phi00 = phi; + lambda *= radians, phi *= radians; + centroidStream$1.point = centroidRingPoint; + var cosPhi = cos$1(phi); + x0$4 = cosPhi * cos$1(lambda); + y0$4 = cosPhi * sin$1(lambda); + z0 = sin$1(phi); + centroidPointCartesian(x0$4, y0$4, z0); +} + +function centroidRingPoint(lambda, phi) { + lambda *= radians, phi *= radians; + var cosPhi = cos$1(phi), + x = cosPhi * cos$1(lambda), + y = cosPhi * sin$1(lambda), + z = sin$1(phi), + cx = y0$4 * z - z0 * y, + cy = z0 * x - x0$4 * z, + cz = x0$4 * y - y0$4 * x, + m = hypot(cx, cy, cz), + w = asin$1(m), // line weight = angle + v = m && -w / m; // area weight multiplier + X2$1.add(v * cx); + Y2$1.add(v * cy); + Z2$1.add(v * cz); + W1 += w; + X1$1 += w * (x0$4 + (x0$4 = x)); + Y1$1 += w * (y0$4 + (y0$4 = y)); + Z1$1 += w * (z0 + (z0 = z)); + centroidPointCartesian(x0$4, y0$4, z0); +} + +function centroid$1(object) { + W0 = W1 = + X0$1 = Y0$1 = Z0$1 = + X1$1 = Y1$1 = Z1$1 = 0; + X2$1 = new Adder(); + Y2$1 = new Adder(); + Z2$1 = new Adder(); + geoStream(object, centroidStream$1); + + var x = +X2$1, + y = +Y2$1, + z = +Z2$1, + m = hypot(x, y, z); + + // If the area-weighted ccentroid is undefined, fall back to length-weighted ccentroid. + if (m < epsilon2) { + x = X1$1, y = Y1$1, z = Z1$1; + // If the feature has zero length, fall back to arithmetic mean of point vectors. + if (W1 < epsilon$1) x = X0$1, y = Y0$1, z = Z0$1; + m = hypot(x, y, z); + // If the feature still has an undefined ccentroid, then return. + if (m < epsilon2) return [NaN, NaN]; + } + + return [atan2$1(y, x) * degrees, asin$1(z / m) * degrees]; +} + +function constant$3(x) { + return function() { + return x; + }; +} + +function compose(a, b) { + + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + + return compose; +} + +function rotationIdentity(lambda, phi) { + if (abs$1(lambda) > pi$1) lambda -= Math.round(lambda / tau$1) * tau$1; + return [lambda, phi]; +} + +rotationIdentity.invert = rotationIdentity; + +function rotateRadians(deltaLambda, deltaPhi, deltaGamma) { + return (deltaLambda %= tau$1) ? (deltaPhi || deltaGamma ? compose(rotationLambda(deltaLambda), rotationPhiGamma(deltaPhi, deltaGamma)) + : rotationLambda(deltaLambda)) + : (deltaPhi || deltaGamma ? rotationPhiGamma(deltaPhi, deltaGamma) + : rotationIdentity); +} + +function forwardRotationLambda(deltaLambda) { + return function(lambda, phi) { + lambda += deltaLambda; + if (abs$1(lambda) > pi$1) lambda -= Math.round(lambda / tau$1) * tau$1; + return [lambda, phi]; + }; +} + +function rotationLambda(deltaLambda) { + var rotation = forwardRotationLambda(deltaLambda); + rotation.invert = forwardRotationLambda(-deltaLambda); + return rotation; +} + +function rotationPhiGamma(deltaPhi, deltaGamma) { + var cosDeltaPhi = cos$1(deltaPhi), + sinDeltaPhi = sin$1(deltaPhi), + cosDeltaGamma = cos$1(deltaGamma), + sinDeltaGamma = sin$1(deltaGamma); + + function rotation(lambda, phi) { + var cosPhi = cos$1(phi), + x = cos$1(lambda) * cosPhi, + y = sin$1(lambda) * cosPhi, + z = sin$1(phi), + k = z * cosDeltaPhi + x * sinDeltaPhi; + return [ + atan2$1(y * cosDeltaGamma - k * sinDeltaGamma, x * cosDeltaPhi - z * sinDeltaPhi), + asin$1(k * cosDeltaGamma + y * sinDeltaGamma) + ]; + } + + rotation.invert = function(lambda, phi) { + var cosPhi = cos$1(phi), + x = cos$1(lambda) * cosPhi, + y = sin$1(lambda) * cosPhi, + z = sin$1(phi), + k = z * cosDeltaGamma - y * sinDeltaGamma; + return [ + atan2$1(y * cosDeltaGamma + z * sinDeltaGamma, x * cosDeltaPhi + k * sinDeltaPhi), + asin$1(k * cosDeltaPhi - x * sinDeltaPhi) + ]; + }; + + return rotation; +} + +function rotation(rotate) { + rotate = rotateRadians(rotate[0] * radians, rotate[1] * radians, rotate.length > 2 ? rotate[2] * radians : 0); + + function forward(coordinates) { + coordinates = rotate(coordinates[0] * radians, coordinates[1] * radians); + return coordinates[0] *= degrees, coordinates[1] *= degrees, coordinates; + } + + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * radians, coordinates[1] * radians); + return coordinates[0] *= degrees, coordinates[1] *= degrees, coordinates; + }; + + return forward; +} + +// Generates a circle centered at [0°, 0°], with a given radius and precision. +function circleStream(stream, radius, delta, direction, t0, t1) { + if (!delta) return; + var cosRadius = cos$1(radius), + sinRadius = sin$1(radius), + step = direction * delta; + if (t0 == null) { + t0 = radius + direction * tau$1; + t1 = radius - step / 2; + } else { + t0 = circleRadius(cosRadius, t0); + t1 = circleRadius(cosRadius, t1); + if (direction > 0 ? t0 < t1 : t0 > t1) t0 += direction * tau$1; + } + for (var point, t = t0; direction > 0 ? t > t1 : t < t1; t -= step) { + point = spherical([cosRadius, -sinRadius * cos$1(t), -sinRadius * sin$1(t)]); + stream.point(point[0], point[1]); + } +} + +// Returns the signed angle of a cartesian point relative to [cosRadius, 0, 0]. +function circleRadius(cosRadius, point) { + point = cartesian(point), point[0] -= cosRadius; + cartesianNormalizeInPlace(point); + var radius = acos$1(-point[1]); + return ((-point[2] < 0 ? -radius : radius) + tau$1 - epsilon$1) % tau$1; +} + +function circle$1() { + var center = constant$3([0, 0]), + radius = constant$3(90), + precision = constant$3(6), + ring, + rotate, + stream = {point: point}; + + function point(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= degrees, x[1] *= degrees; + } + + function circle() { + var c = center.apply(this, arguments), + r = radius.apply(this, arguments) * radians, + p = precision.apply(this, arguments) * radians; + ring = []; + rotate = rotateRadians(-c[0] * radians, -c[1] * radians, 0).invert; + circleStream(stream, r, p, 1); + c = {type: "Polygon", coordinates: [ring]}; + ring = rotate = null; + return c; + } + + circle.center = function(_) { + return arguments.length ? (center = typeof _ === "function" ? _ : constant$3([+_[0], +_[1]]), circle) : center; + }; + + circle.radius = function(_) { + return arguments.length ? (radius = typeof _ === "function" ? _ : constant$3(+_), circle) : radius; + }; + + circle.precision = function(_) { + return arguments.length ? (precision = typeof _ === "function" ? _ : constant$3(+_), circle) : precision; + }; + + return circle; +} + +function clipBuffer() { + var lines = [], + line; + return { + point: function(x, y, m) { + line.push([x, y, m]); + }, + lineStart: function() { + lines.push(line = []); + }, + lineEnd: noop$1, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + }, + result: function() { + var result = lines; + lines = []; + line = null; + return result; + } + }; +} + +function pointEqual(a, b) { + return abs$1(a[0] - b[0]) < epsilon$1 && abs$1(a[1] - b[1]) < epsilon$1; +} + +function Intersection(point, points, other, entry) { + this.x = point; + this.z = points; + this.o = other; // another intersection + this.e = entry; // is an entry? + this.v = false; // visited + this.n = this.p = null; // next & previous +} + +// A generalized polygon clipping algorithm: given a polygon that has been cut +// into its visible line segments, and rejoins the segments by interpolating +// along the clip edge. +function clipRejoin(segments, compareIntersection, startInside, interpolate, stream) { + var subject = [], + clip = [], + i, + n; + + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n], x; + + if (pointEqual(p0, p1)) { + if (!p0[2] && !p1[2]) { + stream.lineStart(); + for (i = 0; i < n; ++i) stream.point((p0 = segment[i])[0], p0[1]); + stream.lineEnd(); + return; + } + // handle degenerate cases by moving the point + p1[0] += 2 * epsilon$1; + } + + subject.push(x = new Intersection(p0, segment, null, true)); + clip.push(x.o = new Intersection(p0, null, x, false)); + subject.push(x = new Intersection(p1, segment, null, false)); + clip.push(x.o = new Intersection(p1, null, x, true)); + }); + + if (!subject.length) return; + + clip.sort(compareIntersection); + link$1(subject); + link$1(clip); + + for (i = 0, n = clip.length; i < n; ++i) { + clip[i].e = startInside = !startInside; + } + + var start = subject[0], + points, + point; + + while (1) { + // Find first unvisited intersection. + var current = start, + isSubject = true; + while (current.v) if ((current = current.n) === start) return; + points = current.z; + stream.lineStart(); + do { + current.v = current.o.v = true; + if (current.e) { + if (isSubject) { + for (i = 0, n = points.length; i < n; ++i) stream.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.n.x, 1, stream); + } + current = current.n; + } else { + if (isSubject) { + points = current.p.z; + for (i = points.length - 1; i >= 0; --i) stream.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.p.x, -1, stream); + } + current = current.p; + } + current = current.o; + points = current.z; + isSubject = !isSubject; + } while (!current.v); + stream.lineEnd(); + } +} + +function link$1(array) { + if (!(n = array.length)) return; + var n, + i = 0, + a = array[0], + b; + while (++i < n) { + a.n = b = array[i]; + b.p = a; + a = b; + } + a.n = b = array[0]; + b.p = a; +} + +function longitude(point) { + return abs$1(point[0]) <= pi$1 ? point[0] : sign$1(point[0]) * ((abs$1(point[0]) + pi$1) % tau$1 - pi$1); +} + +function polygonContains(polygon, point) { + var lambda = longitude(point), + phi = point[1], + sinPhi = sin$1(phi), + normal = [sin$1(lambda), -cos$1(lambda), 0], + angle = 0, + winding = 0; + + var sum = new Adder(); + + if (sinPhi === 1) phi = halfPi$1 + epsilon$1; + else if (sinPhi === -1) phi = -halfPi$1 - epsilon$1; + + for (var i = 0, n = polygon.length; i < n; ++i) { + if (!(m = (ring = polygon[i]).length)) continue; + var ring, + m, + point0 = ring[m - 1], + lambda0 = longitude(point0), + phi0 = point0[1] / 2 + quarterPi, + sinPhi0 = sin$1(phi0), + cosPhi0 = cos$1(phi0); + + for (var j = 0; j < m; ++j, lambda0 = lambda1, sinPhi0 = sinPhi1, cosPhi0 = cosPhi1, point0 = point1) { + var point1 = ring[j], + lambda1 = longitude(point1), + phi1 = point1[1] / 2 + quarterPi, + sinPhi1 = sin$1(phi1), + cosPhi1 = cos$1(phi1), + delta = lambda1 - lambda0, + sign = delta >= 0 ? 1 : -1, + absDelta = sign * delta, + antimeridian = absDelta > pi$1, + k = sinPhi0 * sinPhi1; + + sum.add(atan2$1(k * sign * sin$1(absDelta), cosPhi0 * cosPhi1 + k * cos$1(absDelta))); + angle += antimeridian ? delta + sign * tau$1 : delta; + + // Are the longitudes either side of the point’s meridian (lambda), + // and are the latitudes smaller than the parallel (phi)? + if (antimeridian ^ lambda0 >= lambda ^ lambda1 >= lambda) { + var arc = cartesianCross(cartesian(point0), cartesian(point1)); + cartesianNormalizeInPlace(arc); + var intersection = cartesianCross(normal, arc); + cartesianNormalizeInPlace(intersection); + var phiArc = (antimeridian ^ delta >= 0 ? -1 : 1) * asin$1(intersection[2]); + if (phi > phiArc || phi === phiArc && (arc[0] || arc[1])) { + winding += antimeridian ^ delta >= 0 ? 1 : -1; + } + } + } + } + + // First, determine whether the South pole is inside or outside: + // + // It is inside if: + // * the polygon winds around it in a clockwise direction. + // * the polygon does not (cumulatively) wind around it, but has a negative + // (counter-clockwise) area. + // + // Second, count the (signed) number of times a segment crosses a lambda + // from the point to the South pole. If it is zero, then the point is the + // same side as the South pole. + + return (angle < -epsilon$1 || angle < epsilon$1 && sum < -epsilon2) ^ (winding & 1); +} + +function clip(pointVisible, clipLine, interpolate, start) { + return function(sink) { + var line = clipLine(sink), + ringBuffer = clipBuffer(), + ringSink = clipLine(ringBuffer), + polygonStarted = false, + polygon, + segments, + ring; + + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + segments = []; + polygon = []; + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = merge(segments); + var startInside = polygonContains(polygon, start); + if (segments.length) { + if (!polygonStarted) sink.polygonStart(), polygonStarted = true; + clipRejoin(segments, compareIntersection, startInside, interpolate, sink); + } else if (startInside) { + if (!polygonStarted) sink.polygonStart(), polygonStarted = true; + sink.lineStart(); + interpolate(null, null, 1, sink); + sink.lineEnd(); + } + if (polygonStarted) sink.polygonEnd(), polygonStarted = false; + segments = polygon = null; + }, + sphere: function() { + sink.polygonStart(); + sink.lineStart(); + interpolate(null, null, 1, sink); + sink.lineEnd(); + sink.polygonEnd(); + } + }; + + function point(lambda, phi) { + if (pointVisible(lambda, phi)) sink.point(lambda, phi); + } + + function pointLine(lambda, phi) { + line.point(lambda, phi); + } + + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + + function pointRing(lambda, phi) { + ring.push([lambda, phi]); + ringSink.point(lambda, phi); + } + + function ringStart() { + ringSink.lineStart(); + ring = []; + } + + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringSink.lineEnd(); + + var clean = ringSink.clean(), + ringSegments = ringBuffer.result(), + i, n = ringSegments.length, m, + segment, + point; + + ring.pop(); + polygon.push(ring); + ring = null; + + if (!n) return; + + // No intersections. + if (clean & 1) { + segment = ringSegments[0]; + if ((m = segment.length - 1) > 0) { + if (!polygonStarted) sink.polygonStart(), polygonStarted = true; + sink.lineStart(); + for (i = 0; i < m; ++i) sink.point((point = segment[i])[0], point[1]); + sink.lineEnd(); + } + return; + } + + // Rejoin connected segments. + // TODO reuse ringBuffer.rejoin()? + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + + segments.push(ringSegments.filter(validSegment)); + } + + return clip; + }; +} + +function validSegment(segment) { + return segment.length > 1; +} + +// Intersections are sorted along the clip edge. For both antimeridian cutting +// and circle clipping, the same comparison is used. +function compareIntersection(a, b) { + return ((a = a.x)[0] < 0 ? a[1] - halfPi$1 - epsilon$1 : halfPi$1 - a[1]) + - ((b = b.x)[0] < 0 ? b[1] - halfPi$1 - epsilon$1 : halfPi$1 - b[1]); +} + +var clipAntimeridian = clip( + function() { return true; }, + clipAntimeridianLine, + clipAntimeridianInterpolate, + [-pi$1, -halfPi$1] +); + +// Takes a line and cuts into visible segments. Return values: 0 - there were +// intersections or the line was empty; 1 - no intersections; 2 - there were +// intersections, and the first and last segments should be rejoined. +function clipAntimeridianLine(stream) { + var lambda0 = NaN, + phi0 = NaN, + sign0 = NaN, + clean; // no intersections + + return { + lineStart: function() { + stream.lineStart(); + clean = 1; + }, + point: function(lambda1, phi1) { + var sign1 = lambda1 > 0 ? pi$1 : -pi$1, + delta = abs$1(lambda1 - lambda0); + if (abs$1(delta - pi$1) < epsilon$1) { // line crosses a pole + stream.point(lambda0, phi0 = (phi0 + phi1) / 2 > 0 ? halfPi$1 : -halfPi$1); + stream.point(sign0, phi0); + stream.lineEnd(); + stream.lineStart(); + stream.point(sign1, phi0); + stream.point(lambda1, phi0); + clean = 0; + } else if (sign0 !== sign1 && delta >= pi$1) { // line crosses antimeridian + if (abs$1(lambda0 - sign0) < epsilon$1) lambda0 -= sign0 * epsilon$1; // handle degeneracies + if (abs$1(lambda1 - sign1) < epsilon$1) lambda1 -= sign1 * epsilon$1; + phi0 = clipAntimeridianIntersect(lambda0, phi0, lambda1, phi1); + stream.point(sign0, phi0); + stream.lineEnd(); + stream.lineStart(); + stream.point(sign1, phi0); + clean = 0; + } + stream.point(lambda0 = lambda1, phi0 = phi1); + sign0 = sign1; + }, + lineEnd: function() { + stream.lineEnd(); + lambda0 = phi0 = NaN; + }, + clean: function() { + return 2 - clean; // if intersections, rejoin first and last segments + } + }; +} + +function clipAntimeridianIntersect(lambda0, phi0, lambda1, phi1) { + var cosPhi0, + cosPhi1, + sinLambda0Lambda1 = sin$1(lambda0 - lambda1); + return abs$1(sinLambda0Lambda1) > epsilon$1 + ? atan((sin$1(phi0) * (cosPhi1 = cos$1(phi1)) * sin$1(lambda1) + - sin$1(phi1) * (cosPhi0 = cos$1(phi0)) * sin$1(lambda0)) + / (cosPhi0 * cosPhi1 * sinLambda0Lambda1)) + : (phi0 + phi1) / 2; +} + +function clipAntimeridianInterpolate(from, to, direction, stream) { + var phi; + if (from == null) { + phi = direction * halfPi$1; + stream.point(-pi$1, phi); + stream.point(0, phi); + stream.point(pi$1, phi); + stream.point(pi$1, 0); + stream.point(pi$1, -phi); + stream.point(0, -phi); + stream.point(-pi$1, -phi); + stream.point(-pi$1, 0); + stream.point(-pi$1, phi); + } else if (abs$1(from[0] - to[0]) > epsilon$1) { + var lambda = from[0] < to[0] ? pi$1 : -pi$1; + phi = direction * lambda / 2; + stream.point(-lambda, phi); + stream.point(0, phi); + stream.point(lambda, phi); + } else { + stream.point(to[0], to[1]); + } +} + +function clipCircle(radius) { + var cr = cos$1(radius), + delta = 6 * radians, + smallRadius = cr > 0, + notHemisphere = abs$1(cr) > epsilon$1; // TODO optimise for this common case + + function interpolate(from, to, direction, stream) { + circleStream(stream, radius, delta, direction, from, to); + } + + function visible(lambda, phi) { + return cos$1(lambda) * cos$1(phi) > cr; + } + + // Takes a line and cuts into visible segments. Return values used for polygon + // clipping: 0 - there were intersections or the line was empty; 1 - no + // intersections 2 - there were intersections, and the first and last segments + // should be rejoined. + function clipLine(stream) { + var point0, // previous point + c0, // code for previous point + v0, // visibility of previous point + v00, // visibility of first point + clean; // no intersections + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(lambda, phi) { + var point1 = [lambda, phi], + point2, + v = visible(lambda, phi), + c = smallRadius + ? v ? 0 : code(lambda, phi) + : v ? code(lambda + (lambda < 0 ? pi$1 : -pi$1), phi) : 0; + if (!point0 && (v00 = v0 = v)) stream.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (!point2 || pointEqual(point0, point2) || pointEqual(point1, point2)) + point1[2] = 1; + } + if (v !== v0) { + clean = 0; + if (v) { + // outside going in + stream.lineStart(); + point2 = intersect(point1, point0); + stream.point(point2[0], point2[1]); + } else { + // inside going out + point2 = intersect(point0, point1); + stream.point(point2[0], point2[1], 2); + stream.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + // If the codes for two points are different, or are both zero, + // and there this segment intersects with the small circle. + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + stream.lineStart(); + stream.point(t[0][0], t[0][1]); + stream.point(t[1][0], t[1][1]); + stream.lineEnd(); + } else { + stream.point(t[1][0], t[1][1]); + stream.lineEnd(); + stream.lineStart(); + stream.point(t[0][0], t[0][1], 3); + } + } + } + if (v && (!point0 || !pointEqual(point0, point1))) { + stream.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) stream.lineEnd(); + point0 = null; + }, + // Rejoin first and last segments if there were intersections and the first + // and last points were visible. + clean: function() { + return clean | ((v00 && v0) << 1); + } + }; + } + + // Intersects the great circle between a and b with the clip circle. + function intersect(a, b, two) { + var pa = cartesian(a), + pb = cartesian(b); + + // We have two planes, n1.p = d1 and n2.p = d2. + // Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 ⨯ n2). + var n1 = [1, 0, 0], // normal + n2 = cartesianCross(pa, pb), + n2n2 = cartesianDot(n2, n2), + n1n2 = n2[0], // cartesianDot(n1, n2), + determinant = n2n2 - n1n2 * n1n2; + + // Two polar points. + if (!determinant) return !two && a; + + var c1 = cr * n2n2 / determinant, + c2 = -cr * n1n2 / determinant, + n1xn2 = cartesianCross(n1, n2), + A = cartesianScale(n1, c1), + B = cartesianScale(n2, c2); + cartesianAddInPlace(A, B); + + // Solve |p(t)|^2 = 1. + var u = n1xn2, + w = cartesianDot(A, u), + uu = cartesianDot(u, u), + t2 = w * w - uu * (cartesianDot(A, A) - 1); + + if (t2 < 0) return; + + var t = sqrt$2(t2), + q = cartesianScale(u, (-w - t) / uu); + cartesianAddInPlace(q, A); + q = spherical(q); + + if (!two) return q; + + // Two intersection points. + var lambda0 = a[0], + lambda1 = b[0], + phi0 = a[1], + phi1 = b[1], + z; + + if (lambda1 < lambda0) z = lambda0, lambda0 = lambda1, lambda1 = z; + + var delta = lambda1 - lambda0, + polar = abs$1(delta - pi$1) < epsilon$1, + meridian = polar || delta < epsilon$1; + + if (!polar && phi1 < phi0) z = phi0, phi0 = phi1, phi1 = z; + + // Check that the first point is between a and b. + if (meridian + ? polar + ? phi0 + phi1 > 0 ^ q[1] < (abs$1(q[0] - lambda0) < epsilon$1 ? phi0 : phi1) + : phi0 <= q[1] && q[1] <= phi1 + : delta > pi$1 ^ (lambda0 <= q[0] && q[0] <= lambda1)) { + var q1 = cartesianScale(u, (-w + t) / uu); + cartesianAddInPlace(q1, A); + return [q, spherical(q1)]; + } + } + + // Generates a 4-bit vector representing the location of a point relative to + // the small circle's bounding box. + function code(lambda, phi) { + var r = smallRadius ? radius : pi$1 - radius, + code = 0; + if (lambda < -r) code |= 1; // left + else if (lambda > r) code |= 2; // right + if (phi < -r) code |= 4; // below + else if (phi > r) code |= 8; // above + return code; + } + + return clip(visible, clipLine, interpolate, smallRadius ? [0, -radius] : [-pi$1, radius - pi$1]); +} + +function clipLine(a, b, x0, y0, x1, y1) { + var ax = a[0], + ay = a[1], + bx = b[0], + by = b[1], + t0 = 0, + t1 = 1, + dx = bx - ax, + dy = by - ay, + r; + + r = x0 - ax; + if (!dx && r > 0) return; + r /= dx; + if (dx < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dx > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + + r = x1 - ax; + if (!dx && r < 0) return; + r /= dx; + if (dx < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dx > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + + r = y0 - ay; + if (!dy && r > 0) return; + r /= dy; + if (dy < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dy > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + + r = y1 - ay; + if (!dy && r < 0) return; + r /= dy; + if (dy < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dy > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + + if (t0 > 0) a[0] = ax + t0 * dx, a[1] = ay + t0 * dy; + if (t1 < 1) b[0] = ax + t1 * dx, b[1] = ay + t1 * dy; + return true; +} + +var clipMax = 1e9, clipMin = -clipMax; + +// TODO Use d3-polygon’s polygonContains here for the ring check? +// TODO Eliminate duplicate buffering in clipBuffer and polygon.push? + +function clipRectangle(x0, y0, x1, y1) { + + function visible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + + function interpolate(from, to, direction, stream) { + var a = 0, a1 = 0; + if (from == null + || (a = corner(from, direction)) !== (a1 = corner(to, direction)) + || comparePoint(from, to) < 0 ^ direction > 0) { + do stream.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + while ((a = (a + direction + 4) % 4) !== a1); + } else { + stream.point(to[0], to[1]); + } + } + + function corner(p, direction) { + return abs$1(p[0] - x0) < epsilon$1 ? direction > 0 ? 0 : 3 + : abs$1(p[0] - x1) < epsilon$1 ? direction > 0 ? 2 : 1 + : abs$1(p[1] - y0) < epsilon$1 ? direction > 0 ? 1 : 0 + : direction > 0 ? 3 : 2; // abs(p[1] - y1) < epsilon + } + + function compareIntersection(a, b) { + return comparePoint(a.x, b.x); + } + + function comparePoint(a, b) { + var ca = corner(a, 1), + cb = corner(b, 1); + return ca !== cb ? ca - cb + : ca === 0 ? b[1] - a[1] + : ca === 1 ? a[0] - b[0] + : ca === 2 ? a[1] - b[1] + : b[0] - a[0]; + } + + return function(stream) { + var activeStream = stream, + bufferStream = clipBuffer(), + segments, + polygon, + ring, + x__, y__, v__, // first point + x_, y_, v_, // previous point + first, + clean; + + var clipStream = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: polygonStart, + polygonEnd: polygonEnd + }; + + function point(x, y) { + if (visible(x, y)) activeStream.point(x, y); + } + + function polygonInside() { + var winding = 0; + + for (var i = 0, n = polygon.length; i < n; ++i) { + for (var ring = polygon[i], j = 1, m = ring.length, point = ring[0], a0, a1, b0 = point[0], b1 = point[1]; j < m; ++j) { + a0 = b0, a1 = b1, point = ring[j], b0 = point[0], b1 = point[1]; + if (a1 <= y1) { if (b1 > y1 && (b0 - a0) * (y1 - a1) > (b1 - a1) * (x0 - a0)) ++winding; } + else { if (b1 <= y1 && (b0 - a0) * (y1 - a1) < (b1 - a1) * (x0 - a0)) --winding; } + } + } + + return winding; + } + + // Buffer geometry within a polygon and then clip it en masse. + function polygonStart() { + activeStream = bufferStream, segments = [], polygon = [], clean = true; + } + + function polygonEnd() { + var startInside = polygonInside(), + cleanInside = clean && startInside, + visible = (segments = merge(segments)).length; + if (cleanInside || visible) { + stream.polygonStart(); + if (cleanInside) { + stream.lineStart(); + interpolate(null, null, 1, stream); + stream.lineEnd(); + } + if (visible) { + clipRejoin(segments, compareIntersection, startInside, interpolate, stream); + } + stream.polygonEnd(); + } + activeStream = stream, segments = polygon = ring = null; + } + + function lineStart() { + clipStream.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + + // TODO rather than special-case polygons, simply handle them separately. + // Ideally, coincident intersection points should be jittered to avoid + // clipping issues. + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferStream.rejoin(); + segments.push(bufferStream.result()); + } + clipStream.point = point; + if (v_) activeStream.lineEnd(); + } + + function linePoint(x, y) { + var v = visible(x, y); + if (polygon) ring.push([x, y]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + activeStream.lineStart(); + activeStream.point(x, y); + } + } else { + if (v && v_) activeStream.point(x, y); + else { + var a = [x_ = Math.max(clipMin, Math.min(clipMax, x_)), y_ = Math.max(clipMin, Math.min(clipMax, y_))], + b = [x = Math.max(clipMin, Math.min(clipMax, x)), y = Math.max(clipMin, Math.min(clipMax, y))]; + if (clipLine(a, b, x0, y0, x1, y1)) { + if (!v_) { + activeStream.lineStart(); + activeStream.point(a[0], a[1]); + } + activeStream.point(b[0], b[1]); + if (!v) activeStream.lineEnd(); + clean = false; + } else if (v) { + activeStream.lineStart(); + activeStream.point(x, y); + clean = false; + } + } + } + x_ = x, y_ = y, v_ = v; + } + + return clipStream; + }; +} + +function extent() { + var x0 = 0, + y0 = 0, + x1 = 960, + y1 = 500, + cache, + cacheStream, + clip; + + return clip = { + stream: function(stream) { + return cache && cacheStream === stream ? cache : cache = clipRectangle(x0, y0, x1, y1)(cacheStream = stream); + }, + extent: function(_) { + return arguments.length ? (x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1], cache = cacheStream = null, clip) : [[x0, y0], [x1, y1]]; + } + }; +} + +var lengthSum$1, + lambda0, + sinPhi0, + cosPhi0; + +var lengthStream$1 = { + sphere: noop$1, + point: noop$1, + lineStart: lengthLineStart, + lineEnd: noop$1, + polygonStart: noop$1, + polygonEnd: noop$1 +}; + +function lengthLineStart() { + lengthStream$1.point = lengthPointFirst$1; + lengthStream$1.lineEnd = lengthLineEnd; +} + +function lengthLineEnd() { + lengthStream$1.point = lengthStream$1.lineEnd = noop$1; +} + +function lengthPointFirst$1(lambda, phi) { + lambda *= radians, phi *= radians; + lambda0 = lambda, sinPhi0 = sin$1(phi), cosPhi0 = cos$1(phi); + lengthStream$1.point = lengthPoint$1; +} + +function lengthPoint$1(lambda, phi) { + lambda *= radians, phi *= radians; + var sinPhi = sin$1(phi), + cosPhi = cos$1(phi), + delta = abs$1(lambda - lambda0), + cosDelta = cos$1(delta), + sinDelta = sin$1(delta), + x = cosPhi * sinDelta, + y = cosPhi0 * sinPhi - sinPhi0 * cosPhi * cosDelta, + z = sinPhi0 * sinPhi + cosPhi0 * cosPhi * cosDelta; + lengthSum$1.add(atan2$1(sqrt$2(x * x + y * y), z)); + lambda0 = lambda, sinPhi0 = sinPhi, cosPhi0 = cosPhi; +} + +function length$1(object) { + lengthSum$1 = new Adder(); + geoStream(object, lengthStream$1); + return +lengthSum$1; +} + +var coordinates = [null, null], + object = {type: "LineString", coordinates: coordinates}; + +function distance(a, b) { + coordinates[0] = a; + coordinates[1] = b; + return length$1(object); +} + +var containsObjectType = { + Feature: function(object, point) { + return containsGeometry(object.geometry, point); + }, + FeatureCollection: function(object, point) { + var features = object.features, i = -1, n = features.length; + while (++i < n) if (containsGeometry(features[i].geometry, point)) return true; + return false; + } +}; + +var containsGeometryType = { + Sphere: function() { + return true; + }, + Point: function(object, point) { + return containsPoint(object.coordinates, point); + }, + MultiPoint: function(object, point) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) if (containsPoint(coordinates[i], point)) return true; + return false; + }, + LineString: function(object, point) { + return containsLine(object.coordinates, point); + }, + MultiLineString: function(object, point) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) if (containsLine(coordinates[i], point)) return true; + return false; + }, + Polygon: function(object, point) { + return containsPolygon(object.coordinates, point); + }, + MultiPolygon: function(object, point) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) if (containsPolygon(coordinates[i], point)) return true; + return false; + }, + GeometryCollection: function(object, point) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) if (containsGeometry(geometries[i], point)) return true; + return false; + } +}; + +function containsGeometry(geometry, point) { + return geometry && containsGeometryType.hasOwnProperty(geometry.type) + ? containsGeometryType[geometry.type](geometry, point) + : false; +} + +function containsPoint(coordinates, point) { + return distance(coordinates, point) === 0; +} + +function containsLine(coordinates, point) { + var ao, bo, ab; + for (var i = 0, n = coordinates.length; i < n; i++) { + bo = distance(coordinates[i], point); + if (bo === 0) return true; + if (i > 0) { + ab = distance(coordinates[i], coordinates[i - 1]); + if ( + ab > 0 && + ao <= ab && + bo <= ab && + (ao + bo - ab) * (1 - Math.pow((ao - bo) / ab, 2)) < epsilon2 * ab + ) + return true; + } + ao = bo; + } + return false; +} + +function containsPolygon(coordinates, point) { + return !!polygonContains(coordinates.map(ringRadians), pointRadians(point)); +} + +function ringRadians(ring) { + return ring = ring.map(pointRadians), ring.pop(), ring; +} + +function pointRadians(point) { + return [point[0] * radians, point[1] * radians]; +} + +function contains$1(object, point) { + return (object && containsObjectType.hasOwnProperty(object.type) + ? containsObjectType[object.type] + : containsGeometry)(object, point); +} + +function graticuleX(y0, y1, dy) { + var y = range$2(y0, y1 - epsilon$1, dy).concat(y1); + return function(x) { return y.map(function(y) { return [x, y]; }); }; +} + +function graticuleY(x0, x1, dx) { + var x = range$2(x0, x1 - epsilon$1, dx).concat(x1); + return function(y) { return x.map(function(x) { return [x, y]; }); }; +} + +function graticule() { + var x1, x0, X1, X0, + y1, y0, Y1, Y0, + dx = 10, dy = dx, DX = 90, DY = 360, + x, y, X, Y, + precision = 2.5; + + function graticule() { + return {type: "MultiLineString", coordinates: lines()}; + } + + function lines() { + return range$2(ceil(X0 / DX) * DX, X1, DX).map(X) + .concat(range$2(ceil(Y0 / DY) * DY, Y1, DY).map(Y)) + .concat(range$2(ceil(x0 / dx) * dx, x1, dx).filter(function(x) { return abs$1(x % DX) > epsilon$1; }).map(x)) + .concat(range$2(ceil(y0 / dy) * dy, y1, dy).filter(function(y) { return abs$1(y % DY) > epsilon$1; }).map(y)); + } + + graticule.lines = function() { + return lines().map(function(coordinates) { return {type: "LineString", coordinates: coordinates}; }); + }; + + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ + X(X0).concat( + Y(Y1).slice(1), + X(X1).reverse().slice(1), + Y(Y0).reverse().slice(1)) + ] + }; + }; + + graticule.extent = function(_) { + if (!arguments.length) return graticule.extentMinor(); + return graticule.extentMajor(_).extentMinor(_); + }; + + graticule.extentMajor = function(_) { + if (!arguments.length) return [[X0, Y0], [X1, Y1]]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + + graticule.extentMinor = function(_) { + if (!arguments.length) return [[x0, y0], [x1, y1]]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + + graticule.step = function(_) { + if (!arguments.length) return graticule.stepMinor(); + return graticule.stepMajor(_).stepMinor(_); + }; + + graticule.stepMajor = function(_) { + if (!arguments.length) return [DX, DY]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + + graticule.stepMinor = function(_) { + if (!arguments.length) return [dx, dy]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = graticuleX(y0, y1, 90); + y = graticuleY(x0, x1, precision); + X = graticuleX(Y0, Y1, 90); + Y = graticuleY(X0, X1, precision); + return graticule; + }; + + return graticule + .extentMajor([[-180, -90 + epsilon$1], [180, 90 - epsilon$1]]) + .extentMinor([[-180, -80 - epsilon$1], [180, 80 + epsilon$1]]); +} + +function graticule10() { + return graticule()(); +} + +function interpolate(a, b) { + var x0 = a[0] * radians, + y0 = a[1] * radians, + x1 = b[0] * radians, + y1 = b[1] * radians, + cy0 = cos$1(y0), + sy0 = sin$1(y0), + cy1 = cos$1(y1), + sy1 = sin$1(y1), + kx0 = cy0 * cos$1(x0), + ky0 = cy0 * sin$1(x0), + kx1 = cy1 * cos$1(x1), + ky1 = cy1 * sin$1(x1), + d = 2 * asin$1(sqrt$2(haversin(y1 - y0) + cy0 * cy1 * haversin(x1 - x0))), + k = sin$1(d); + + var interpolate = d ? function(t) { + var B = sin$1(t *= d) / k, + A = sin$1(d - t) / k, + x = A * kx0 + B * kx1, + y = A * ky0 + B * ky1, + z = A * sy0 + B * sy1; + return [ + atan2$1(y, x) * degrees, + atan2$1(z, sqrt$2(x * x + y * y)) * degrees + ]; + } : function() { + return [x0 * degrees, y0 * degrees]; + }; + + interpolate.distance = d; + + return interpolate; +} + +var identity$5 = x => x; + +var areaSum = new Adder(), + areaRingSum = new Adder(), + x00$2, + y00$2, + x0$3, + y0$3; + +var areaStream = { + point: noop$1, + lineStart: noop$1, + lineEnd: noop$1, + polygonStart: function() { + areaStream.lineStart = areaRingStart; + areaStream.lineEnd = areaRingEnd; + }, + polygonEnd: function() { + areaStream.lineStart = areaStream.lineEnd = areaStream.point = noop$1; + areaSum.add(abs$1(areaRingSum)); + areaRingSum = new Adder(); + }, + result: function() { + var area = areaSum / 2; + areaSum = new Adder(); + return area; + } +}; + +function areaRingStart() { + areaStream.point = areaPointFirst; +} + +function areaPointFirst(x, y) { + areaStream.point = areaPoint; + x00$2 = x0$3 = x, y00$2 = y0$3 = y; +} + +function areaPoint(x, y) { + areaRingSum.add(y0$3 * x - x0$3 * y); + x0$3 = x, y0$3 = y; +} + +function areaRingEnd() { + areaPoint(x00$2, y00$2); +} + +var pathArea = areaStream; + +var x0$2 = Infinity, + y0$2 = x0$2, + x1 = -x0$2, + y1 = x1; + +var boundsStream = { + point: boundsPoint, + lineStart: noop$1, + lineEnd: noop$1, + polygonStart: noop$1, + polygonEnd: noop$1, + result: function() { + var bounds = [[x0$2, y0$2], [x1, y1]]; + x1 = y1 = -(y0$2 = x0$2 = Infinity); + return bounds; + } +}; + +function boundsPoint(x, y) { + if (x < x0$2) x0$2 = x; + if (x > x1) x1 = x; + if (y < y0$2) y0$2 = y; + if (y > y1) y1 = y; +} + +var boundsStream$1 = boundsStream; + +// TODO Enforce positive area for exterior, negative area for interior? + +var X0 = 0, + Y0 = 0, + Z0 = 0, + X1 = 0, + Y1 = 0, + Z1 = 0, + X2 = 0, + Y2 = 0, + Z2 = 0, + x00$1, + y00$1, + x0$1, + y0$1; + +var centroidStream = { + point: centroidPoint, + lineStart: centroidLineStart, + lineEnd: centroidLineEnd, + polygonStart: function() { + centroidStream.lineStart = centroidRingStart; + centroidStream.lineEnd = centroidRingEnd; + }, + polygonEnd: function() { + centroidStream.point = centroidPoint; + centroidStream.lineStart = centroidLineStart; + centroidStream.lineEnd = centroidLineEnd; + }, + result: function() { + var centroid = Z2 ? [X2 / Z2, Y2 / Z2] + : Z1 ? [X1 / Z1, Y1 / Z1] + : Z0 ? [X0 / Z0, Y0 / Z0] + : [NaN, NaN]; + X0 = Y0 = Z0 = + X1 = Y1 = Z1 = + X2 = Y2 = Z2 = 0; + return centroid; + } +}; + +function centroidPoint(x, y) { + X0 += x; + Y0 += y; + ++Z0; +} + +function centroidLineStart() { + centroidStream.point = centroidPointFirstLine; +} + +function centroidPointFirstLine(x, y) { + centroidStream.point = centroidPointLine; + centroidPoint(x0$1 = x, y0$1 = y); +} + +function centroidPointLine(x, y) { + var dx = x - x0$1, dy = y - y0$1, z = sqrt$2(dx * dx + dy * dy); + X1 += z * (x0$1 + x) / 2; + Y1 += z * (y0$1 + y) / 2; + Z1 += z; + centroidPoint(x0$1 = x, y0$1 = y); +} + +function centroidLineEnd() { + centroidStream.point = centroidPoint; +} + +function centroidRingStart() { + centroidStream.point = centroidPointFirstRing; +} + +function centroidRingEnd() { + centroidPointRing(x00$1, y00$1); +} + +function centroidPointFirstRing(x, y) { + centroidStream.point = centroidPointRing; + centroidPoint(x00$1 = x0$1 = x, y00$1 = y0$1 = y); +} + +function centroidPointRing(x, y) { + var dx = x - x0$1, + dy = y - y0$1, + z = sqrt$2(dx * dx + dy * dy); + + X1 += z * (x0$1 + x) / 2; + Y1 += z * (y0$1 + y) / 2; + Z1 += z; + + z = y0$1 * x - x0$1 * y; + X2 += z * (x0$1 + x); + Y2 += z * (y0$1 + y); + Z2 += z * 3; + centroidPoint(x0$1 = x, y0$1 = y); +} + +var pathCentroid = centroidStream; + +function PathContext(context) { + this._context = context; +} + +PathContext.prototype = { + _radius: 4.5, + pointRadius: function(_) { + return this._radius = _, this; + }, + polygonStart: function() { + this._line = 0; + }, + polygonEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._point = 0; + }, + lineEnd: function() { + if (this._line === 0) this._context.closePath(); + this._point = NaN; + }, + point: function(x, y) { + switch (this._point) { + case 0: { + this._context.moveTo(x, y); + this._point = 1; + break; + } + case 1: { + this._context.lineTo(x, y); + break; + } + default: { + this._context.moveTo(x + this._radius, y); + this._context.arc(x, y, this._radius, 0, tau$1); + break; + } + } + }, + result: noop$1 +}; + +var lengthSum = new Adder(), + lengthRing, + x00, + y00, + x0, + y0; + +var lengthStream = { + point: noop$1, + lineStart: function() { + lengthStream.point = lengthPointFirst; + }, + lineEnd: function() { + if (lengthRing) lengthPoint(x00, y00); + lengthStream.point = noop$1; + }, + polygonStart: function() { + lengthRing = true; + }, + polygonEnd: function() { + lengthRing = null; + }, + result: function() { + var length = +lengthSum; + lengthSum = new Adder(); + return length; + } +}; + +function lengthPointFirst(x, y) { + lengthStream.point = lengthPoint; + x00 = x0 = x, y00 = y0 = y; +} + +function lengthPoint(x, y) { + x0 -= x, y0 -= y; + lengthSum.add(sqrt$2(x0 * x0 + y0 * y0)); + x0 = x, y0 = y; +} + +var pathMeasure = lengthStream; + +// Simple caching for constant-radius points. +let cacheDigits, cacheAppend, cacheRadius, cacheCircle; + +class PathString { + constructor(digits) { + this._append = digits == null ? append : appendRound(digits); + this._radius = 4.5; + this._ = ""; + } + pointRadius(_) { + this._radius = +_; + return this; + } + polygonStart() { + this._line = 0; + } + polygonEnd() { + this._line = NaN; + } + lineStart() { + this._point = 0; + } + lineEnd() { + if (this._line === 0) this._ += "Z"; + this._point = NaN; + } + point(x, y) { + switch (this._point) { + case 0: { + this._append`M${x},${y}`; + this._point = 1; + break; + } + case 1: { + this._append`L${x},${y}`; + break; + } + default: { + this._append`M${x},${y}`; + if (this._radius !== cacheRadius || this._append !== cacheAppend) { + const r = this._radius; + const s = this._; + this._ = ""; // stash the old string so we can cache the circle path fragment + this._append`m0,${r}a${r},${r} 0 1,1 0,${-2 * r}a${r},${r} 0 1,1 0,${2 * r}z`; + cacheRadius = r; + cacheAppend = this._append; + cacheCircle = this._; + this._ = s; + } + this._ += cacheCircle; + break; + } + } + } + result() { + const result = this._; + this._ = ""; + return result.length ? result : null; + } +} + +function append(strings) { + let i = 1; + this._ += strings[0]; + for (const j = strings.length; i < j; ++i) { + this._ += arguments[i] + strings[i]; + } +} + +function appendRound(digits) { + const d = Math.floor(digits); + if (!(d >= 0)) throw new RangeError(`invalid digits: ${digits}`); + if (d > 15) return append; + if (d !== cacheDigits) { + const k = 10 ** d; + cacheDigits = d; + cacheAppend = function append(strings) { + let i = 1; + this._ += strings[0]; + for (const j = strings.length; i < j; ++i) { + this._ += Math.round(arguments[i] * k) / k + strings[i]; + } + }; + } + return cacheAppend; +} + +function index$2(projection, context) { + let digits = 3, + pointRadius = 4.5, + projectionStream, + contextStream; + + function path(object) { + if (object) { + if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments)); + geoStream(object, projectionStream(contextStream)); + } + return contextStream.result(); + } + + path.area = function(object) { + geoStream(object, projectionStream(pathArea)); + return pathArea.result(); + }; + + path.measure = function(object) { + geoStream(object, projectionStream(pathMeasure)); + return pathMeasure.result(); + }; + + path.bounds = function(object) { + geoStream(object, projectionStream(boundsStream$1)); + return boundsStream$1.result(); + }; + + path.centroid = function(object) { + geoStream(object, projectionStream(pathCentroid)); + return pathCentroid.result(); + }; + + path.projection = function(_) { + if (!arguments.length) return projection; + projectionStream = _ == null ? (projection = null, identity$5) : (projection = _).stream; + return path; + }; + + path.context = function(_) { + if (!arguments.length) return context; + contextStream = _ == null ? (context = null, new PathString(digits)) : new PathContext(context = _); + if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); + return path; + }; + + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); + return path; + }; + + path.digits = function(_) { + if (!arguments.length) return digits; + if (_ == null) digits = null; + else { + const d = Math.floor(_); + if (!(d >= 0)) throw new RangeError(`invalid digits: ${_}`); + digits = d; + } + if (context === null) contextStream = new PathString(digits); + return path; + }; + + return path.projection(projection).digits(digits).context(context); +} + +function transform$1(methods) { + return { + stream: transformer$3(methods) + }; +} + +function transformer$3(methods) { + return function(stream) { + var s = new TransformStream; + for (var key in methods) s[key] = methods[key]; + s.stream = stream; + return s; + }; +} + +function TransformStream() {} + +TransformStream.prototype = { + constructor: TransformStream, + point: function(x, y) { this.stream.point(x, y); }, + sphere: function() { this.stream.sphere(); }, + lineStart: function() { this.stream.lineStart(); }, + lineEnd: function() { this.stream.lineEnd(); }, + polygonStart: function() { this.stream.polygonStart(); }, + polygonEnd: function() { this.stream.polygonEnd(); } +}; + +function fit(projection, fitBounds, object) { + var clip = projection.clipExtent && projection.clipExtent(); + projection.scale(150).translate([0, 0]); + if (clip != null) projection.clipExtent(null); + geoStream(object, projection.stream(boundsStream$1)); + fitBounds(boundsStream$1.result()); + if (clip != null) projection.clipExtent(clip); + return projection; +} + +function fitExtent(projection, extent, object) { + return fit(projection, function(b) { + var w = extent[1][0] - extent[0][0], + h = extent[1][1] - extent[0][1], + k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1])), + x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2, + y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2; + projection.scale(150 * k).translate([x, y]); + }, object); +} + +function fitSize(projection, size, object) { + return fitExtent(projection, [[0, 0], size], object); +} + +function fitWidth(projection, width, object) { + return fit(projection, function(b) { + var w = +width, + k = w / (b[1][0] - b[0][0]), + x = (w - k * (b[1][0] + b[0][0])) / 2, + y = -k * b[0][1]; + projection.scale(150 * k).translate([x, y]); + }, object); +} + +function fitHeight(projection, height, object) { + return fit(projection, function(b) { + var h = +height, + k = h / (b[1][1] - b[0][1]), + x = -k * b[0][0], + y = (h - k * (b[1][1] + b[0][1])) / 2; + projection.scale(150 * k).translate([x, y]); + }, object); +} + +var maxDepth = 16, // maximum depth of subdivision + cosMinDistance = cos$1(30 * radians); // cos(minimum angular distance) + +function resample(project, delta2) { + return +delta2 ? resample$1(project, delta2) : resampleNone(project); +} + +function resampleNone(project) { + return transformer$3({ + point: function(x, y) { + x = project(x, y); + this.stream.point(x[0], x[1]); + } + }); +} + +function resample$1(project, delta2) { + + function resampleLineTo(x0, y0, lambda0, a0, b0, c0, x1, y1, lambda1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, + dy = y1 - y0, + d2 = dx * dx + dy * dy; + if (d2 > 4 * delta2 && depth--) { + var a = a0 + a1, + b = b0 + b1, + c = c0 + c1, + m = sqrt$2(a * a + b * b + c * c), + phi2 = asin$1(c /= m), + lambda2 = abs$1(abs$1(c) - 1) < epsilon$1 || abs$1(lambda0 - lambda1) < epsilon$1 ? (lambda0 + lambda1) / 2 : atan2$1(b, a), + p = project(lambda2, phi2), + x2 = p[0], + y2 = p[1], + dx2 = x2 - x0, + dy2 = y2 - y0, + dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > delta2 // perpendicular projected distance + || abs$1((dx * dx2 + dy * dy2) / d2 - 0.5) > 0.3 // midpoint close to an end + || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { // angular distance + resampleLineTo(x0, y0, lambda0, a0, b0, c0, x2, y2, lambda2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, lambda2, a, b, c, x1, y1, lambda1, a1, b1, c1, depth, stream); + } + } + } + return function(stream) { + var lambda00, x00, y00, a00, b00, c00, // first point + lambda0, x0, y0, a0, b0, c0; // previous point + + var resampleStream = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { stream.polygonStart(); resampleStream.lineStart = ringStart; }, + polygonEnd: function() { stream.polygonEnd(); resampleStream.lineStart = lineStart; } + }; + + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + + function lineStart() { + x0 = NaN; + resampleStream.point = linePoint; + stream.lineStart(); + } + + function linePoint(lambda, phi) { + var c = cartesian([lambda, phi]), p = project(lambda, phi); + resampleLineTo(x0, y0, lambda0, a0, b0, c0, x0 = p[0], y0 = p[1], lambda0 = lambda, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + + function lineEnd() { + resampleStream.point = point; + stream.lineEnd(); + } + + function ringStart() { + lineStart(); + resampleStream.point = ringPoint; + resampleStream.lineEnd = ringEnd; + } + + function ringPoint(lambda, phi) { + linePoint(lambda00 = lambda, phi), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resampleStream.point = linePoint; + } + + function ringEnd() { + resampleLineTo(x0, y0, lambda0, a0, b0, c0, x00, y00, lambda00, a00, b00, c00, maxDepth, stream); + resampleStream.lineEnd = lineEnd; + lineEnd(); + } + + return resampleStream; + }; +} + +var transformRadians = transformer$3({ + point: function(x, y) { + this.stream.point(x * radians, y * radians); + } +}); + +function transformRotate(rotate) { + return transformer$3({ + point: function(x, y) { + var r = rotate(x, y); + return this.stream.point(r[0], r[1]); + } + }); +} + +function scaleTranslate(k, dx, dy, sx, sy) { + function transform(x, y) { + x *= sx; y *= sy; + return [dx + k * x, dy - k * y]; + } + transform.invert = function(x, y) { + return [(x - dx) / k * sx, (dy - y) / k * sy]; + }; + return transform; +} + +function scaleTranslateRotate(k, dx, dy, sx, sy, alpha) { + if (!alpha) return scaleTranslate(k, dx, dy, sx, sy); + var cosAlpha = cos$1(alpha), + sinAlpha = sin$1(alpha), + a = cosAlpha * k, + b = sinAlpha * k, + ai = cosAlpha / k, + bi = sinAlpha / k, + ci = (sinAlpha * dy - cosAlpha * dx) / k, + fi = (sinAlpha * dx + cosAlpha * dy) / k; + function transform(x, y) { + x *= sx; y *= sy; + return [a * x - b * y + dx, dy - b * x - a * y]; + } + transform.invert = function(x, y) { + return [sx * (ai * x - bi * y + ci), sy * (fi - bi * x - ai * y)]; + }; + return transform; +} + +function projection(project) { + return projectionMutator(function() { return project; })(); +} + +function projectionMutator(projectAt) { + var project, + k = 150, // scale + x = 480, y = 250, // translate + lambda = 0, phi = 0, // center + deltaLambda = 0, deltaPhi = 0, deltaGamma = 0, rotate, // pre-rotate + alpha = 0, // post-rotate angle + sx = 1, // reflectX + sy = 1, // reflectX + theta = null, preclip = clipAntimeridian, // pre-clip angle + x0 = null, y0, x1, y1, postclip = identity$5, // post-clip extent + delta2 = 0.5, // precision + projectResample, + projectTransform, + projectRotateTransform, + cache, + cacheStream; + + function projection(point) { + return projectRotateTransform(point[0] * radians, point[1] * radians); + } + + function invert(point) { + point = projectRotateTransform.invert(point[0], point[1]); + return point && [point[0] * degrees, point[1] * degrees]; + } + + projection.stream = function(stream) { + return cache && cacheStream === stream ? cache : cache = transformRadians(transformRotate(rotate)(preclip(projectResample(postclip(cacheStream = stream))))); + }; + + projection.preclip = function(_) { + return arguments.length ? (preclip = _, theta = undefined, reset()) : preclip; + }; + + projection.postclip = function(_) { + return arguments.length ? (postclip = _, x0 = y0 = x1 = y1 = null, reset()) : postclip; + }; + + projection.clipAngle = function(_) { + return arguments.length ? (preclip = +_ ? clipCircle(theta = _ * radians) : (theta = null, clipAntimeridian), reset()) : theta * degrees; + }; + + projection.clipExtent = function(_) { + return arguments.length ? (postclip = _ == null ? (x0 = y0 = x1 = y1 = null, identity$5) : clipRectangle(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]), reset()) : x0 == null ? null : [[x0, y0], [x1, y1]]; + }; + + projection.scale = function(_) { + return arguments.length ? (k = +_, recenter()) : k; + }; + + projection.translate = function(_) { + return arguments.length ? (x = +_[0], y = +_[1], recenter()) : [x, y]; + }; + + projection.center = function(_) { + return arguments.length ? (lambda = _[0] % 360 * radians, phi = _[1] % 360 * radians, recenter()) : [lambda * degrees, phi * degrees]; + }; + + projection.rotate = function(_) { + return arguments.length ? (deltaLambda = _[0] % 360 * radians, deltaPhi = _[1] % 360 * radians, deltaGamma = _.length > 2 ? _[2] % 360 * radians : 0, recenter()) : [deltaLambda * degrees, deltaPhi * degrees, deltaGamma * degrees]; + }; + + projection.angle = function(_) { + return arguments.length ? (alpha = _ % 360 * radians, recenter()) : alpha * degrees; + }; + + projection.reflectX = function(_) { + return arguments.length ? (sx = _ ? -1 : 1, recenter()) : sx < 0; + }; + + projection.reflectY = function(_) { + return arguments.length ? (sy = _ ? -1 : 1, recenter()) : sy < 0; + }; + + projection.precision = function(_) { + return arguments.length ? (projectResample = resample(projectTransform, delta2 = _ * _), reset()) : sqrt$2(delta2); + }; + + projection.fitExtent = function(extent, object) { + return fitExtent(projection, extent, object); + }; + + projection.fitSize = function(size, object) { + return fitSize(projection, size, object); + }; + + projection.fitWidth = function(width, object) { + return fitWidth(projection, width, object); + }; + + projection.fitHeight = function(height, object) { + return fitHeight(projection, height, object); + }; + + function recenter() { + var center = scaleTranslateRotate(k, 0, 0, sx, sy, alpha).apply(null, project(lambda, phi)), + transform = scaleTranslateRotate(k, x - center[0], y - center[1], sx, sy, alpha); + rotate = rotateRadians(deltaLambda, deltaPhi, deltaGamma); + projectTransform = compose(project, transform); + projectRotateTransform = compose(rotate, projectTransform); + projectResample = resample(projectTransform, delta2); + return reset(); + } + + function reset() { + cache = cacheStream = null; + return projection; + } + + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return recenter(); + }; +} + +function conicProjection(projectAt) { + var phi0 = 0, + phi1 = pi$1 / 3, + m = projectionMutator(projectAt), + p = m(phi0, phi1); + + p.parallels = function(_) { + return arguments.length ? m(phi0 = _[0] * radians, phi1 = _[1] * radians) : [phi0 * degrees, phi1 * degrees]; + }; + + return p; +} + +function cylindricalEqualAreaRaw(phi0) { + var cosPhi0 = cos$1(phi0); + + function forward(lambda, phi) { + return [lambda * cosPhi0, sin$1(phi) / cosPhi0]; + } + + forward.invert = function(x, y) { + return [x / cosPhi0, asin$1(y * cosPhi0)]; + }; + + return forward; +} + +function conicEqualAreaRaw(y0, y1) { + var sy0 = sin$1(y0), n = (sy0 + sin$1(y1)) / 2; + + // Are the parallels symmetrical around the Equator? + if (abs$1(n) < epsilon$1) return cylindricalEqualAreaRaw(y0); + + var c = 1 + sy0 * (2 * n - sy0), r0 = sqrt$2(c) / n; + + function project(x, y) { + var r = sqrt$2(c - 2 * n * sin$1(y)) / n; + return [r * sin$1(x *= n), r0 - r * cos$1(x)]; + } + + project.invert = function(x, y) { + var r0y = r0 - y, + l = atan2$1(x, abs$1(r0y)) * sign$1(r0y); + if (r0y * n < 0) + l -= pi$1 * sign$1(x) * sign$1(r0y); + return [l / n, asin$1((c - (x * x + r0y * r0y) * n * n) / (2 * n))]; + }; + + return project; +} + +function conicEqualArea() { + return conicProjection(conicEqualAreaRaw) + .scale(155.424) + .center([0, 33.6442]); +} + +function albers() { + return conicEqualArea() + .parallels([29.5, 45.5]) + .scale(1070) + .translate([480, 250]) + .rotate([96, 0]) + .center([-0.6, 38.7]); +} + +// The projections must have mutually exclusive clip regions on the sphere, +// as this will avoid emitting interleaving lines and polygons. +function multiplex(streams) { + var n = streams.length; + return { + point: function(x, y) { var i = -1; while (++i < n) streams[i].point(x, y); }, + sphere: function() { var i = -1; while (++i < n) streams[i].sphere(); }, + lineStart: function() { var i = -1; while (++i < n) streams[i].lineStart(); }, + lineEnd: function() { var i = -1; while (++i < n) streams[i].lineEnd(); }, + polygonStart: function() { var i = -1; while (++i < n) streams[i].polygonStart(); }, + polygonEnd: function() { var i = -1; while (++i < n) streams[i].polygonEnd(); } + }; +} + +// A composite projection for the United States, configured by default for +// 960×500. The projection also works quite well at 960×600 if you change the +// scale to 1285 and adjust the translate accordingly. The set of standard +// parallels for each region comes from USGS, which is published here: +// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers +function albersUsa() { + var cache, + cacheStream, + lower48 = albers(), lower48Point, + alaska = conicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]), alaskaPoint, // EPSG:3338 + hawaii = conicEqualArea().rotate([157, 0]).center([-3, 19.9]).parallels([8, 18]), hawaiiPoint, // ESRI:102007 + point, pointStream = {point: function(x, y) { point = [x, y]; }}; + + function albersUsa(coordinates) { + var x = coordinates[0], y = coordinates[1]; + return point = null, + (lower48Point.point(x, y), point) + || (alaskaPoint.point(x, y), point) + || (hawaiiPoint.point(x, y), point); + } + + albersUsa.invert = function(coordinates) { + var k = lower48.scale(), + t = lower48.translate(), + x = (coordinates[0] - t[0]) / k, + y = (coordinates[1] - t[1]) / k; + return (y >= 0.120 && y < 0.234 && x >= -0.425 && x < -0.214 ? alaska + : y >= 0.166 && y < 0.234 && x >= -0.214 && x < -0.115 ? hawaii + : lower48).invert(coordinates); + }; + + albersUsa.stream = function(stream) { + return cache && cacheStream === stream ? cache : cache = multiplex([lower48.stream(cacheStream = stream), alaska.stream(stream), hawaii.stream(stream)]); + }; + + albersUsa.precision = function(_) { + if (!arguments.length) return lower48.precision(); + lower48.precision(_), alaska.precision(_), hawaii.precision(_); + return reset(); + }; + + albersUsa.scale = function(_) { + if (!arguments.length) return lower48.scale(); + lower48.scale(_), alaska.scale(_ * 0.35), hawaii.scale(_); + return albersUsa.translate(lower48.translate()); + }; + + albersUsa.translate = function(_) { + if (!arguments.length) return lower48.translate(); + var k = lower48.scale(), x = +_[0], y = +_[1]; + + lower48Point = lower48 + .translate(_) + .clipExtent([[x - 0.455 * k, y - 0.238 * k], [x + 0.455 * k, y + 0.238 * k]]) + .stream(pointStream); + + alaskaPoint = alaska + .translate([x - 0.307 * k, y + 0.201 * k]) + .clipExtent([[x - 0.425 * k + epsilon$1, y + 0.120 * k + epsilon$1], [x - 0.214 * k - epsilon$1, y + 0.234 * k - epsilon$1]]) + .stream(pointStream); + + hawaiiPoint = hawaii + .translate([x - 0.205 * k, y + 0.212 * k]) + .clipExtent([[x - 0.214 * k + epsilon$1, y + 0.166 * k + epsilon$1], [x - 0.115 * k - epsilon$1, y + 0.234 * k - epsilon$1]]) + .stream(pointStream); + + return reset(); + }; + + albersUsa.fitExtent = function(extent, object) { + return fitExtent(albersUsa, extent, object); + }; + + albersUsa.fitSize = function(size, object) { + return fitSize(albersUsa, size, object); + }; + + albersUsa.fitWidth = function(width, object) { + return fitWidth(albersUsa, width, object); + }; + + albersUsa.fitHeight = function(height, object) { + return fitHeight(albersUsa, height, object); + }; + + function reset() { + cache = cacheStream = null; + return albersUsa; + } + + return albersUsa.scale(1070); +} + +function azimuthalRaw(scale) { + return function(x, y) { + var cx = cos$1(x), + cy = cos$1(y), + k = scale(cx * cy); + if (k === Infinity) return [2, 0]; + return [ + k * cy * sin$1(x), + k * sin$1(y) + ]; + } +} + +function azimuthalInvert(angle) { + return function(x, y) { + var z = sqrt$2(x * x + y * y), + c = angle(z), + sc = sin$1(c), + cc = cos$1(c); + return [ + atan2$1(x * sc, z * cc), + asin$1(z && y * sc / z) + ]; + } +} + +var azimuthalEqualAreaRaw = azimuthalRaw(function(cxcy) { + return sqrt$2(2 / (1 + cxcy)); +}); + +azimuthalEqualAreaRaw.invert = azimuthalInvert(function(z) { + return 2 * asin$1(z / 2); +}); + +function azimuthalEqualArea() { + return projection(azimuthalEqualAreaRaw) + .scale(124.75) + .clipAngle(180 - 1e-3); +} + +var azimuthalEquidistantRaw = azimuthalRaw(function(c) { + return (c = acos$1(c)) && c / sin$1(c); +}); + +azimuthalEquidistantRaw.invert = azimuthalInvert(function(z) { + return z; +}); + +function azimuthalEquidistant() { + return projection(azimuthalEquidistantRaw) + .scale(79.4188) + .clipAngle(180 - 1e-3); +} + +function mercatorRaw(lambda, phi) { + return [lambda, log$1(tan((halfPi$1 + phi) / 2))]; +} + +mercatorRaw.invert = function(x, y) { + return [x, 2 * atan(exp(y)) - halfPi$1]; +}; + +function mercator() { + return mercatorProjection(mercatorRaw) + .scale(961 / tau$1); +} + +function mercatorProjection(project) { + var m = projection(project), + center = m.center, + scale = m.scale, + translate = m.translate, + clipExtent = m.clipExtent, + x0 = null, y0, x1, y1; // clip extent + + m.scale = function(_) { + return arguments.length ? (scale(_), reclip()) : scale(); + }; + + m.translate = function(_) { + return arguments.length ? (translate(_), reclip()) : translate(); + }; + + m.center = function(_) { + return arguments.length ? (center(_), reclip()) : center(); + }; + + m.clipExtent = function(_) { + return arguments.length ? ((_ == null ? x0 = y0 = x1 = y1 = null : (x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1])), reclip()) : x0 == null ? null : [[x0, y0], [x1, y1]]; + }; + + function reclip() { + var k = pi$1 * scale(), + t = m(rotation(m.rotate()).invert([0, 0])); + return clipExtent(x0 == null + ? [[t[0] - k, t[1] - k], [t[0] + k, t[1] + k]] : project === mercatorRaw + ? [[Math.max(t[0] - k, x0), y0], [Math.min(t[0] + k, x1), y1]] + : [[x0, Math.max(t[1] - k, y0)], [x1, Math.min(t[1] + k, y1)]]); + } + + return reclip(); +} + +function tany(y) { + return tan((halfPi$1 + y) / 2); +} + +function conicConformalRaw(y0, y1) { + var cy0 = cos$1(y0), + n = y0 === y1 ? sin$1(y0) : log$1(cy0 / cos$1(y1)) / log$1(tany(y1) / tany(y0)), + f = cy0 * pow$1(tany(y0), n) / n; + + if (!n) return mercatorRaw; + + function project(x, y) { + if (f > 0) { if (y < -halfPi$1 + epsilon$1) y = -halfPi$1 + epsilon$1; } + else { if (y > halfPi$1 - epsilon$1) y = halfPi$1 - epsilon$1; } + var r = f / pow$1(tany(y), n); + return [r * sin$1(n * x), f - r * cos$1(n * x)]; + } + + project.invert = function(x, y) { + var fy = f - y, r = sign$1(n) * sqrt$2(x * x + fy * fy), + l = atan2$1(x, abs$1(fy)) * sign$1(fy); + if (fy * n < 0) + l -= pi$1 * sign$1(x) * sign$1(fy); + return [l / n, 2 * atan(pow$1(f / r, 1 / n)) - halfPi$1]; + }; + + return project; +} + +function conicConformal() { + return conicProjection(conicConformalRaw) + .scale(109.5) + .parallels([30, 30]); +} + +function equirectangularRaw(lambda, phi) { + return [lambda, phi]; +} + +equirectangularRaw.invert = equirectangularRaw; + +function equirectangular() { + return projection(equirectangularRaw) + .scale(152.63); +} + +function conicEquidistantRaw(y0, y1) { + var cy0 = cos$1(y0), + n = y0 === y1 ? sin$1(y0) : (cy0 - cos$1(y1)) / (y1 - y0), + g = cy0 / n + y0; + + if (abs$1(n) < epsilon$1) return equirectangularRaw; + + function project(x, y) { + var gy = g - y, nx = n * x; + return [gy * sin$1(nx), g - gy * cos$1(nx)]; + } + + project.invert = function(x, y) { + var gy = g - y, + l = atan2$1(x, abs$1(gy)) * sign$1(gy); + if (gy * n < 0) + l -= pi$1 * sign$1(x) * sign$1(gy); + return [l / n, g - sign$1(n) * sqrt$2(x * x + gy * gy)]; + }; + + return project; +} + +function conicEquidistant() { + return conicProjection(conicEquidistantRaw) + .scale(131.154) + .center([0, 13.9389]); +} + +var A1 = 1.340264, + A2 = -0.081106, + A3 = 0.000893, + A4 = 0.003796, + M = sqrt$2(3) / 2, + iterations = 12; + +function equalEarthRaw(lambda, phi) { + var l = asin$1(M * sin$1(phi)), l2 = l * l, l6 = l2 * l2 * l2; + return [ + lambda * cos$1(l) / (M * (A1 + 3 * A2 * l2 + l6 * (7 * A3 + 9 * A4 * l2))), + l * (A1 + A2 * l2 + l6 * (A3 + A4 * l2)) + ]; +} + +equalEarthRaw.invert = function(x, y) { + var l = y, l2 = l * l, l6 = l2 * l2 * l2; + for (var i = 0, delta, fy, fpy; i < iterations; ++i) { + fy = l * (A1 + A2 * l2 + l6 * (A3 + A4 * l2)) - y; + fpy = A1 + 3 * A2 * l2 + l6 * (7 * A3 + 9 * A4 * l2); + l -= delta = fy / fpy, l2 = l * l, l6 = l2 * l2 * l2; + if (abs$1(delta) < epsilon2) break; + } + return [ + M * x * (A1 + 3 * A2 * l2 + l6 * (7 * A3 + 9 * A4 * l2)) / cos$1(l), + asin$1(sin$1(l) / M) + ]; +}; + +function equalEarth() { + return projection(equalEarthRaw) + .scale(177.158); +} + +function gnomonicRaw(x, y) { + var cy = cos$1(y), k = cos$1(x) * cy; + return [cy * sin$1(x) / k, sin$1(y) / k]; +} + +gnomonicRaw.invert = azimuthalInvert(atan); + +function gnomonic() { + return projection(gnomonicRaw) + .scale(144.049) + .clipAngle(60); +} + +function identity$4() { + var k = 1, tx = 0, ty = 0, sx = 1, sy = 1, // scale, translate and reflect + alpha = 0, ca, sa, // angle + x0 = null, y0, x1, y1, // clip extent + kx = 1, ky = 1, + transform = transformer$3({ + point: function(x, y) { + var p = projection([x, y]); + this.stream.point(p[0], p[1]); + } + }), + postclip = identity$5, + cache, + cacheStream; + + function reset() { + kx = k * sx; + ky = k * sy; + cache = cacheStream = null; + return projection; + } + + function projection (p) { + var x = p[0] * kx, y = p[1] * ky; + if (alpha) { + var t = y * ca - x * sa; + x = x * ca + y * sa; + y = t; + } + return [x + tx, y + ty]; + } + projection.invert = function(p) { + var x = p[0] - tx, y = p[1] - ty; + if (alpha) { + var t = y * ca + x * sa; + x = x * ca - y * sa; + y = t; + } + return [x / kx, y / ky]; + }; + projection.stream = function(stream) { + return cache && cacheStream === stream ? cache : cache = transform(postclip(cacheStream = stream)); + }; + projection.postclip = function(_) { + return arguments.length ? (postclip = _, x0 = y0 = x1 = y1 = null, reset()) : postclip; + }; + projection.clipExtent = function(_) { + return arguments.length ? (postclip = _ == null ? (x0 = y0 = x1 = y1 = null, identity$5) : clipRectangle(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]), reset()) : x0 == null ? null : [[x0, y0], [x1, y1]]; + }; + projection.scale = function(_) { + return arguments.length ? (k = +_, reset()) : k; + }; + projection.translate = function(_) { + return arguments.length ? (tx = +_[0], ty = +_[1], reset()) : [tx, ty]; + }; + projection.angle = function(_) { + return arguments.length ? (alpha = _ % 360 * radians, sa = sin$1(alpha), ca = cos$1(alpha), reset()) : alpha * degrees; + }; + projection.reflectX = function(_) { + return arguments.length ? (sx = _ ? -1 : 1, reset()) : sx < 0; + }; + projection.reflectY = function(_) { + return arguments.length ? (sy = _ ? -1 : 1, reset()) : sy < 0; + }; + projection.fitExtent = function(extent, object) { + return fitExtent(projection, extent, object); + }; + projection.fitSize = function(size, object) { + return fitSize(projection, size, object); + }; + projection.fitWidth = function(width, object) { + return fitWidth(projection, width, object); + }; + projection.fitHeight = function(height, object) { + return fitHeight(projection, height, object); + }; + + return projection; +} + +function naturalEarth1Raw(lambda, phi) { + var phi2 = phi * phi, phi4 = phi2 * phi2; + return [ + lambda * (0.8707 - 0.131979 * phi2 + phi4 * (-0.013791 + phi4 * (0.003971 * phi2 - 0.001529 * phi4))), + phi * (1.007226 + phi2 * (0.015085 + phi4 * (-0.044475 + 0.028874 * phi2 - 0.005916 * phi4))) + ]; +} + +naturalEarth1Raw.invert = function(x, y) { + var phi = y, i = 25, delta; + do { + var phi2 = phi * phi, phi4 = phi2 * phi2; + phi -= delta = (phi * (1.007226 + phi2 * (0.015085 + phi4 * (-0.044475 + 0.028874 * phi2 - 0.005916 * phi4))) - y) / + (1.007226 + phi2 * (0.015085 * 3 + phi4 * (-0.044475 * 7 + 0.028874 * 9 * phi2 - 0.005916 * 11 * phi4))); + } while (abs$1(delta) > epsilon$1 && --i > 0); + return [ + x / (0.8707 + (phi2 = phi * phi) * (-0.131979 + phi2 * (-0.013791 + phi2 * phi2 * phi2 * (0.003971 - 0.001529 * phi2)))), + phi + ]; +}; + +function naturalEarth1() { + return projection(naturalEarth1Raw) + .scale(175.295); +} + +function orthographicRaw(x, y) { + return [cos$1(y) * sin$1(x), sin$1(y)]; +} + +orthographicRaw.invert = azimuthalInvert(asin$1); + +function orthographic() { + return projection(orthographicRaw) + .scale(249.5) + .clipAngle(90 + epsilon$1); +} + +function stereographicRaw(x, y) { + var cy = cos$1(y), k = 1 + cos$1(x) * cy; + return [cy * sin$1(x) / k, sin$1(y) / k]; +} + +stereographicRaw.invert = azimuthalInvert(function(z) { + return 2 * atan(z); +}); + +function stereographic() { + return projection(stereographicRaw) + .scale(250) + .clipAngle(142); +} + +function transverseMercatorRaw(lambda, phi) { + return [log$1(tan((halfPi$1 + phi) / 2)), -lambda]; +} + +transverseMercatorRaw.invert = function(x, y) { + return [-y, 2 * atan(exp(x)) - halfPi$1]; +}; + +function transverseMercator() { + var m = mercatorProjection(transverseMercatorRaw), + center = m.center, + rotate = m.rotate; + + m.center = function(_) { + return arguments.length ? center([-_[1], _[0]]) : (_ = center(), [_[1], -_[0]]); + }; + + m.rotate = function(_) { + return arguments.length ? rotate([_[0], _[1], _.length > 2 ? _[2] + 90 : 90]) : (_ = rotate(), [_[0], _[1], _[2] - 90]); + }; + + return rotate([0, 0, 90]) + .scale(159.155); +} + +function defaultSeparation$1(a, b) { + return a.parent === b.parent ? 1 : 2; +} + +function meanX(children) { + return children.reduce(meanXReduce, 0) / children.length; +} + +function meanXReduce(x, c) { + return x + c.x; +} + +function maxY(children) { + return 1 + children.reduce(maxYReduce, 0); +} + +function maxYReduce(y, c) { + return Math.max(y, c.y); +} + +function leafLeft(node) { + var children; + while (children = node.children) node = children[0]; + return node; +} + +function leafRight(node) { + var children; + while (children = node.children) node = children[children.length - 1]; + return node; +} + +function cluster() { + var separation = defaultSeparation$1, + dx = 1, + dy = 1, + nodeSize = false; + + function cluster(root) { + var previousNode, + x = 0; + + // First walk, computing the initial x & y values. + root.eachAfter(function(node) { + var children = node.children; + if (children) { + node.x = meanX(children); + node.y = maxY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + + var left = leafLeft(root), + right = leafRight(root), + x0 = left.x - separation(left, right) / 2, + x1 = right.x + separation(right, left) / 2; + + // Second walk, normalizing x & y to the desired size. + return root.eachAfter(nodeSize ? function(node) { + node.x = (node.x - root.x) * dx; + node.y = (root.y - node.y) * dy; + } : function(node) { + node.x = (node.x - x0) / (x1 - x0) * dx; + node.y = (1 - (root.y ? node.y / root.y : 1)) * dy; + }); + } + + cluster.separation = function(x) { + return arguments.length ? (separation = x, cluster) : separation; + }; + + cluster.size = function(x) { + return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], cluster) : (nodeSize ? null : [dx, dy]); + }; + + cluster.nodeSize = function(x) { + return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], cluster) : (nodeSize ? [dx, dy] : null); + }; + + return cluster; +} + +function count(node) { + var sum = 0, + children = node.children, + i = children && children.length; + if (!i) sum = 1; + else while (--i >= 0) sum += children[i].value; + node.value = sum; +} + +function node_count() { + return this.eachAfter(count); +} + +function node_each(callback, that) { + let index = -1; + for (const node of this) { + callback.call(that, node, ++index, this); + } + return this; +} + +function node_eachBefore(callback, that) { + var node = this, nodes = [node], children, i, index = -1; + while (node = nodes.pop()) { + callback.call(that, node, ++index, this); + if (children = node.children) { + for (i = children.length - 1; i >= 0; --i) { + nodes.push(children[i]); + } + } + } + return this; +} + +function node_eachAfter(callback, that) { + var node = this, nodes = [node], next = [], children, i, n, index = -1; + while (node = nodes.pop()) { + next.push(node); + if (children = node.children) { + for (i = 0, n = children.length; i < n; ++i) { + nodes.push(children[i]); + } + } + } + while (node = next.pop()) { + callback.call(that, node, ++index, this); + } + return this; +} + +function node_find(callback, that) { + let index = -1; + for (const node of this) { + if (callback.call(that, node, ++index, this)) { + return node; + } + } +} + +function node_sum(value) { + return this.eachAfter(function(node) { + var sum = +value(node.data) || 0, + children = node.children, + i = children && children.length; + while (--i >= 0) sum += children[i].value; + node.value = sum; + }); +} + +function node_sort(compare) { + return this.eachBefore(function(node) { + if (node.children) { + node.children.sort(compare); + } + }); +} + +function node_path(end) { + var start = this, + ancestor = leastCommonAncestor(start, end), + nodes = [start]; + while (start !== ancestor) { + start = start.parent; + nodes.push(start); + } + var k = nodes.length; + while (end !== ancestor) { + nodes.splice(k, 0, end); + end = end.parent; + } + return nodes; +} + +function leastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = a.ancestors(), + bNodes = b.ancestors(), + c = null; + a = aNodes.pop(); + b = bNodes.pop(); + while (a === b) { + c = a; + a = aNodes.pop(); + b = bNodes.pop(); + } + return c; +} + +function node_ancestors() { + var node = this, nodes = [node]; + while (node = node.parent) { + nodes.push(node); + } + return nodes; +} + +function node_descendants() { + return Array.from(this); +} + +function node_leaves() { + var leaves = []; + this.eachBefore(function(node) { + if (!node.children) { + leaves.push(node); + } + }); + return leaves; +} + +function node_links() { + var root = this, links = []; + root.each(function(node) { + if (node !== root) { // Don’t include the root’s parent, if any. + links.push({source: node.parent, target: node}); + } + }); + return links; +} + +function* node_iterator() { + var node = this, current, next = [node], children, i, n; + do { + current = next.reverse(), next = []; + while (node = current.pop()) { + yield node; + if (children = node.children) { + for (i = 0, n = children.length; i < n; ++i) { + next.push(children[i]); + } + } + } + } while (next.length); +} + +function hierarchy(data, children) { + if (data instanceof Map) { + data = [undefined, data]; + if (children === undefined) children = mapChildren; + } else if (children === undefined) { + children = objectChildren; + } + + var root = new Node$1(data), + node, + nodes = [root], + child, + childs, + i, + n; + + while (node = nodes.pop()) { + if ((childs = children(node.data)) && (n = (childs = Array.from(childs)).length)) { + node.children = childs; + for (i = n - 1; i >= 0; --i) { + nodes.push(child = childs[i] = new Node$1(childs[i])); + child.parent = node; + child.depth = node.depth + 1; + } + } + } + + return root.eachBefore(computeHeight); +} + +function node_copy() { + return hierarchy(this).eachBefore(copyData); +} + +function objectChildren(d) { + return d.children; +} + +function mapChildren(d) { + return Array.isArray(d) ? d[1] : null; +} + +function copyData(node) { + if (node.data.value !== undefined) node.value = node.data.value; + node.data = node.data.data; +} + +function computeHeight(node) { + var height = 0; + do node.height = height; + while ((node = node.parent) && (node.height < ++height)); +} + +function Node$1(data) { + this.data = data; + this.depth = + this.height = 0; + this.parent = null; +} + +Node$1.prototype = hierarchy.prototype = { + constructor: Node$1, + count: node_count, + each: node_each, + eachAfter: node_eachAfter, + eachBefore: node_eachBefore, + find: node_find, + sum: node_sum, + sort: node_sort, + path: node_path, + ancestors: node_ancestors, + descendants: node_descendants, + leaves: node_leaves, + links: node_links, + copy: node_copy, + [Symbol.iterator]: node_iterator +}; + +function optional(f) { + return f == null ? null : required(f); +} + +function required(f) { + if (typeof f !== "function") throw new Error; + return f; +} + +function constantZero() { + return 0; +} + +function constant$2(x) { + return function() { + return x; + }; +} + +// https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use +const a$1 = 1664525; +const c$3 = 1013904223; +const m = 4294967296; // 2^32 + +function lcg$1() { + let s = 1; + return () => (s = (a$1 * s + c$3) % m) / m; +} + +function array$1(x) { + return typeof x === "object" && "length" in x + ? x // Array, TypedArray, NodeList, array-like + : Array.from(x); // Map, Set, iterable, string, or anything else +} + +function shuffle(array, random) { + let m = array.length, + t, + i; + + while (m) { + i = random() * m-- | 0; + t = array[m]; + array[m] = array[i]; + array[i] = t; + } + + return array; +} + +function enclose(circles) { + return packEncloseRandom(circles, lcg$1()); +} + +function packEncloseRandom(circles, random) { + var i = 0, n = (circles = shuffle(Array.from(circles), random)).length, B = [], p, e; + + while (i < n) { + p = circles[i]; + if (e && enclosesWeak(e, p)) ++i; + else e = encloseBasis(B = extendBasis(B, p)), i = 0; + } + + return e; +} + +function extendBasis(B, p) { + var i, j; + + if (enclosesWeakAll(p, B)) return [p]; + + // If we get here then B must have at least one element. + for (i = 0; i < B.length; ++i) { + if (enclosesNot(p, B[i]) + && enclosesWeakAll(encloseBasis2(B[i], p), B)) { + return [B[i], p]; + } + } + + // If we get here then B must have at least two elements. + for (i = 0; i < B.length - 1; ++i) { + for (j = i + 1; j < B.length; ++j) { + if (enclosesNot(encloseBasis2(B[i], B[j]), p) + && enclosesNot(encloseBasis2(B[i], p), B[j]) + && enclosesNot(encloseBasis2(B[j], p), B[i]) + && enclosesWeakAll(encloseBasis3(B[i], B[j], p), B)) { + return [B[i], B[j], p]; + } + } + } + + // If we get here then something is very wrong. + throw new Error; +} + +function enclosesNot(a, b) { + var dr = a.r - b.r, dx = b.x - a.x, dy = b.y - a.y; + return dr < 0 || dr * dr < dx * dx + dy * dy; +} + +function enclosesWeak(a, b) { + var dr = a.r - b.r + Math.max(a.r, b.r, 1) * 1e-9, dx = b.x - a.x, dy = b.y - a.y; + return dr > 0 && dr * dr > dx * dx + dy * dy; +} + +function enclosesWeakAll(a, B) { + for (var i = 0; i < B.length; ++i) { + if (!enclosesWeak(a, B[i])) { + return false; + } + } + return true; +} + +function encloseBasis(B) { + switch (B.length) { + case 1: return encloseBasis1(B[0]); + case 2: return encloseBasis2(B[0], B[1]); + case 3: return encloseBasis3(B[0], B[1], B[2]); + } +} + +function encloseBasis1(a) { + return { + x: a.x, + y: a.y, + r: a.r + }; +} + +function encloseBasis2(a, b) { + var x1 = a.x, y1 = a.y, r1 = a.r, + x2 = b.x, y2 = b.y, r2 = b.r, + x21 = x2 - x1, y21 = y2 - y1, r21 = r2 - r1, + l = Math.sqrt(x21 * x21 + y21 * y21); + return { + x: (x1 + x2 + x21 / l * r21) / 2, + y: (y1 + y2 + y21 / l * r21) / 2, + r: (l + r1 + r2) / 2 + }; +} + +function encloseBasis3(a, b, c) { + var x1 = a.x, y1 = a.y, r1 = a.r, + x2 = b.x, y2 = b.y, r2 = b.r, + x3 = c.x, y3 = c.y, r3 = c.r, + a2 = x1 - x2, + a3 = x1 - x3, + b2 = y1 - y2, + b3 = y1 - y3, + c2 = r2 - r1, + c3 = r3 - r1, + d1 = x1 * x1 + y1 * y1 - r1 * r1, + d2 = d1 - x2 * x2 - y2 * y2 + r2 * r2, + d3 = d1 - x3 * x3 - y3 * y3 + r3 * r3, + ab = a3 * b2 - a2 * b3, + xa = (b2 * d3 - b3 * d2) / (ab * 2) - x1, + xb = (b3 * c2 - b2 * c3) / ab, + ya = (a3 * d2 - a2 * d3) / (ab * 2) - y1, + yb = (a2 * c3 - a3 * c2) / ab, + A = xb * xb + yb * yb - 1, + B = 2 * (r1 + xa * xb + ya * yb), + C = xa * xa + ya * ya - r1 * r1, + r = -(Math.abs(A) > 1e-6 ? (B + Math.sqrt(B * B - 4 * A * C)) / (2 * A) : C / B); + return { + x: x1 + xa + xb * r, + y: y1 + ya + yb * r, + r: r + }; +} + +function place(b, a, c) { + var dx = b.x - a.x, x, a2, + dy = b.y - a.y, y, b2, + d2 = dx * dx + dy * dy; + if (d2) { + a2 = a.r + c.r, a2 *= a2; + b2 = b.r + c.r, b2 *= b2; + if (a2 > b2) { + x = (d2 + b2 - a2) / (2 * d2); + y = Math.sqrt(Math.max(0, b2 / d2 - x * x)); + c.x = b.x - x * dx - y * dy; + c.y = b.y - x * dy + y * dx; + } else { + x = (d2 + a2 - b2) / (2 * d2); + y = Math.sqrt(Math.max(0, a2 / d2 - x * x)); + c.x = a.x + x * dx - y * dy; + c.y = a.y + x * dy + y * dx; + } + } else { + c.x = a.x + c.r; + c.y = a.y; + } +} + +function intersects(a, b) { + var dr = a.r + b.r - 1e-6, dx = b.x - a.x, dy = b.y - a.y; + return dr > 0 && dr * dr > dx * dx + dy * dy; +} + +function score(node) { + var a = node._, + b = node.next._, + ab = a.r + b.r, + dx = (a.x * b.r + b.x * a.r) / ab, + dy = (a.y * b.r + b.y * a.r) / ab; + return dx * dx + dy * dy; +} + +function Node(circle) { + this._ = circle; + this.next = null; + this.previous = null; +} + +function packSiblingsRandom(circles, random) { + if (!(n = (circles = array$1(circles)).length)) return 0; + + var a, b, c, n, aa, ca, i, j, k, sj, sk; + + // Place the first circle. + a = circles[0], a.x = 0, a.y = 0; + if (!(n > 1)) return a.r; + + // Place the second circle. + b = circles[1], a.x = -b.r, b.x = a.r, b.y = 0; + if (!(n > 2)) return a.r + b.r; + + // Place the third circle. + place(b, a, c = circles[2]); + + // Initialize the front-chain using the first three circles a, b and c. + a = new Node(a), b = new Node(b), c = new Node(c); + a.next = c.previous = b; + b.next = a.previous = c; + c.next = b.previous = a; + + // Attempt to place each remaining circle… + pack: for (i = 3; i < n; ++i) { + place(a._, b._, c = circles[i]), c = new Node(c); + + // Find the closest intersecting circle on the front-chain, if any. + // “Closeness” is determined by linear distance along the front-chain. + // “Ahead” or “behind” is likewise determined by linear distance. + j = b.next, k = a.previous, sj = b._.r, sk = a._.r; + do { + if (sj <= sk) { + if (intersects(j._, c._)) { + b = j, a.next = b, b.previous = a, --i; + continue pack; + } + sj += j._.r, j = j.next; + } else { + if (intersects(k._, c._)) { + a = k, a.next = b, b.previous = a, --i; + continue pack; + } + sk += k._.r, k = k.previous; + } + } while (j !== k.next); + + // Success! Insert the new circle c between a and b. + c.previous = a, c.next = b, a.next = b.previous = b = c; + + // Compute the new closest circle pair to the centroid. + aa = score(a); + while ((c = c.next) !== b) { + if ((ca = score(c)) < aa) { + a = c, aa = ca; + } + } + b = a.next; + } + + // Compute the enclosing circle of the front chain. + a = [b._], c = b; while ((c = c.next) !== b) a.push(c._); c = packEncloseRandom(a, random); + + // Translate the circles to put the enclosing circle around the origin. + for (i = 0; i < n; ++i) a = circles[i], a.x -= c.x, a.y -= c.y; + + return c.r; +} + +function siblings(circles) { + packSiblingsRandom(circles, lcg$1()); + return circles; +} + +function defaultRadius(d) { + return Math.sqrt(d.value); +} + +function index$1() { + var radius = null, + dx = 1, + dy = 1, + padding = constantZero; + + function pack(root) { + const random = lcg$1(); + root.x = dx / 2, root.y = dy / 2; + if (radius) { + root.eachBefore(radiusLeaf(radius)) + .eachAfter(packChildrenRandom(padding, 0.5, random)) + .eachBefore(translateChild(1)); + } else { + root.eachBefore(radiusLeaf(defaultRadius)) + .eachAfter(packChildrenRandom(constantZero, 1, random)) + .eachAfter(packChildrenRandom(padding, root.r / Math.min(dx, dy), random)) + .eachBefore(translateChild(Math.min(dx, dy) / (2 * root.r))); + } + return root; + } + + pack.radius = function(x) { + return arguments.length ? (radius = optional(x), pack) : radius; + }; + + pack.size = function(x) { + return arguments.length ? (dx = +x[0], dy = +x[1], pack) : [dx, dy]; + }; + + pack.padding = function(x) { + return arguments.length ? (padding = typeof x === "function" ? x : constant$2(+x), pack) : padding; + }; + + return pack; +} + +function radiusLeaf(radius) { + return function(node) { + if (!node.children) { + node.r = Math.max(0, +radius(node) || 0); + } + }; +} + +function packChildrenRandom(padding, k, random) { + return function(node) { + if (children = node.children) { + var children, + i, + n = children.length, + r = padding(node) * k || 0, + e; + + if (r) for (i = 0; i < n; ++i) children[i].r += r; + e = packSiblingsRandom(children, random); + if (r) for (i = 0; i < n; ++i) children[i].r -= r; + node.r = e + r; + } + }; +} + +function translateChild(k) { + return function(node) { + var parent = node.parent; + node.r *= k; + if (parent) { + node.x = parent.x + k * node.x; + node.y = parent.y + k * node.y; + } + }; +} + +function roundNode(node) { + node.x0 = Math.round(node.x0); + node.y0 = Math.round(node.y0); + node.x1 = Math.round(node.x1); + node.y1 = Math.round(node.y1); +} + +function treemapDice(parent, x0, y0, x1, y1) { + var nodes = parent.children, + node, + i = -1, + n = nodes.length, + k = parent.value && (x1 - x0) / parent.value; + + while (++i < n) { + node = nodes[i], node.y0 = y0, node.y1 = y1; + node.x0 = x0, node.x1 = x0 += node.value * k; + } +} + +function partition() { + var dx = 1, + dy = 1, + padding = 0, + round = false; + + function partition(root) { + var n = root.height + 1; + root.x0 = + root.y0 = padding; + root.x1 = dx; + root.y1 = dy / n; + root.eachBefore(positionNode(dy, n)); + if (round) root.eachBefore(roundNode); + return root; + } + + function positionNode(dy, n) { + return function(node) { + if (node.children) { + treemapDice(node, node.x0, dy * (node.depth + 1) / n, node.x1, dy * (node.depth + 2) / n); + } + var x0 = node.x0, + y0 = node.y0, + x1 = node.x1 - padding, + y1 = node.y1 - padding; + if (x1 < x0) x0 = x1 = (x0 + x1) / 2; + if (y1 < y0) y0 = y1 = (y0 + y1) / 2; + node.x0 = x0; + node.y0 = y0; + node.x1 = x1; + node.y1 = y1; + }; + } + + partition.round = function(x) { + return arguments.length ? (round = !!x, partition) : round; + }; + + partition.size = function(x) { + return arguments.length ? (dx = +x[0], dy = +x[1], partition) : [dx, dy]; + }; + + partition.padding = function(x) { + return arguments.length ? (padding = +x, partition) : padding; + }; + + return partition; +} + +var preroot = {depth: -1}, + ambiguous = {}, + imputed = {}; + +function defaultId(d) { + return d.id; +} + +function defaultParentId(d) { + return d.parentId; +} + +function stratify() { + var id = defaultId, + parentId = defaultParentId, + path; + + function stratify(data) { + var nodes = Array.from(data), + currentId = id, + currentParentId = parentId, + n, + d, + i, + root, + parent, + node, + nodeId, + nodeKey, + nodeByKey = new Map; + + if (path != null) { + const I = nodes.map((d, i) => normalize$1(path(d, i, data))); + const P = I.map(parentof); + const S = new Set(I).add(""); + for (const i of P) { + if (!S.has(i)) { + S.add(i); + I.push(i); + P.push(parentof(i)); + nodes.push(imputed); + } + } + currentId = (_, i) => I[i]; + currentParentId = (_, i) => P[i]; + } + + for (i = 0, n = nodes.length; i < n; ++i) { + d = nodes[i], node = nodes[i] = new Node$1(d); + if ((nodeId = currentId(d, i, data)) != null && (nodeId += "")) { + nodeKey = node.id = nodeId; + nodeByKey.set(nodeKey, nodeByKey.has(nodeKey) ? ambiguous : node); + } + if ((nodeId = currentParentId(d, i, data)) != null && (nodeId += "")) { + node.parent = nodeId; + } + } + + for (i = 0; i < n; ++i) { + node = nodes[i]; + if (nodeId = node.parent) { + parent = nodeByKey.get(nodeId); + if (!parent) throw new Error("missing: " + nodeId); + if (parent === ambiguous) throw new Error("ambiguous: " + nodeId); + if (parent.children) parent.children.push(node); + else parent.children = [node]; + node.parent = parent; + } else { + if (root) throw new Error("multiple roots"); + root = node; + } + } + + if (!root) throw new Error("no root"); + + // When imputing internal nodes, only introduce roots if needed. + // Then replace the imputed marker data with null. + if (path != null) { + while (root.data === imputed && root.children.length === 1) { + root = root.children[0], --n; + } + for (let i = nodes.length - 1; i >= 0; --i) { + node = nodes[i]; + if (node.data !== imputed) break; + node.data = null; + } + } + + root.parent = preroot; + root.eachBefore(function(node) { node.depth = node.parent.depth + 1; --n; }).eachBefore(computeHeight); + root.parent = null; + if (n > 0) throw new Error("cycle"); + + return root; + } + + stratify.id = function(x) { + return arguments.length ? (id = optional(x), stratify) : id; + }; + + stratify.parentId = function(x) { + return arguments.length ? (parentId = optional(x), stratify) : parentId; + }; + + stratify.path = function(x) { + return arguments.length ? (path = optional(x), stratify) : path; + }; + + return stratify; +} + +// To normalize a path, we coerce to a string, strip the trailing slash if any +// (as long as the trailing slash is not immediately preceded by another slash), +// and add leading slash if missing. +function normalize$1(path) { + path = `${path}`; + let i = path.length; + if (slash(path, i - 1) && !slash(path, i - 2)) path = path.slice(0, -1); + return path[0] === "/" ? path : `/${path}`; +} + +// Walk backwards to find the first slash that is not the leading slash, e.g.: +// "/foo/bar" ⇥ "/foo", "/foo" ⇥ "/", "/" ↦ "". (The root is special-cased +// because the id of the root must be a truthy value.) +function parentof(path) { + let i = path.length; + if (i < 2) return ""; + while (--i > 1) if (slash(path, i)) break; + return path.slice(0, i); +} + +// Slashes can be escaped; to determine whether a slash is a path delimiter, we +// count the number of preceding backslashes escaping the forward slash: an odd +// number indicates an escaped forward slash. +function slash(path, i) { + if (path[i] === "/") { + let k = 0; + while (i > 0 && path[--i] === "\\") ++k; + if ((k & 1) === 0) return true; + } + return false; +} + +function defaultSeparation(a, b) { + return a.parent === b.parent ? 1 : 2; +} + +// function radialSeparation(a, b) { +// return (a.parent === b.parent ? 1 : 2) / a.depth; +// } + +// This function is used to traverse the left contour of a subtree (or +// subforest). It returns the successor of v on this contour. This successor is +// either given by the leftmost child of v or by the thread of v. The function +// returns null if and only if v is on the highest level of its subtree. +function nextLeft(v) { + var children = v.children; + return children ? children[0] : v.t; +} + +// This function works analogously to nextLeft. +function nextRight(v) { + var children = v.children; + return children ? children[children.length - 1] : v.t; +} + +// Shifts the current subtree rooted at w+. This is done by increasing +// prelim(w+) and mod(w+) by shift. +function moveSubtree(wm, wp, shift) { + var change = shift / (wp.i - wm.i); + wp.c -= change; + wp.s += shift; + wm.c += change; + wp.z += shift; + wp.m += shift; +} + +// All other shifts, applied to the smaller subtrees between w- and w+, are +// performed by this function. To prepare the shifts, we have to adjust +// change(w+), shift(w+), and change(w-). +function executeShifts(v) { + var shift = 0, + change = 0, + children = v.children, + i = children.length, + w; + while (--i >= 0) { + w = children[i]; + w.z += shift; + w.m += shift; + shift += w.s + (change += w.c); + } +} + +// If vi-’s ancestor is a sibling of v, returns vi-’s ancestor. Otherwise, +// returns the specified (default) ancestor. +function nextAncestor(vim, v, ancestor) { + return vim.a.parent === v.parent ? vim.a : ancestor; +} + +function TreeNode(node, i) { + this._ = node; + this.parent = null; + this.children = null; + this.A = null; // default ancestor + this.a = this; // ancestor + this.z = 0; // prelim + this.m = 0; // mod + this.c = 0; // change + this.s = 0; // shift + this.t = null; // thread + this.i = i; // number +} + +TreeNode.prototype = Object.create(Node$1.prototype); + +function treeRoot(root) { + var tree = new TreeNode(root, 0), + node, + nodes = [tree], + child, + children, + i, + n; + + while (node = nodes.pop()) { + if (children = node._.children) { + node.children = new Array(n = children.length); + for (i = n - 1; i >= 0; --i) { + nodes.push(child = node.children[i] = new TreeNode(children[i], i)); + child.parent = node; + } + } + } + + (tree.parent = new TreeNode(null, 0)).children = [tree]; + return tree; +} + +// Node-link tree diagram using the Reingold-Tilford "tidy" algorithm +function tree() { + var separation = defaultSeparation, + dx = 1, + dy = 1, + nodeSize = null; + + function tree(root) { + var t = treeRoot(root); + + // Compute the layout using Buchheim et al.’s algorithm. + t.eachAfter(firstWalk), t.parent.m = -t.z; + t.eachBefore(secondWalk); + + // If a fixed node size is specified, scale x and y. + if (nodeSize) root.eachBefore(sizeNode); + + // If a fixed tree size is specified, scale x and y based on the extent. + // Compute the left-most, right-most, and depth-most nodes for extents. + else { + var left = root, + right = root, + bottom = root; + root.eachBefore(function(node) { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + if (node.depth > bottom.depth) bottom = node; + }); + var s = left === right ? 1 : separation(left, right) / 2, + tx = s - left.x, + kx = dx / (right.x + s + tx), + ky = dy / (bottom.depth || 1); + root.eachBefore(function(node) { + node.x = (node.x + tx) * kx; + node.y = node.depth * ky; + }); + } + + return root; + } + + // Computes a preliminary x-coordinate for v. Before that, FIRST WALK is + // applied recursively to the children of v, as well as the function + // APPORTION. After spacing out the children by calling EXECUTE SHIFTS, the + // node v is placed to the midpoint of its outermost children. + function firstWalk(v) { + var children = v.children, + siblings = v.parent.children, + w = v.i ? siblings[v.i - 1] : null; + if (children) { + executeShifts(v); + var midpoint = (children[0].z + children[children.length - 1].z) / 2; + if (w) { + v.z = w.z + separation(v._, w._); + v.m = v.z - midpoint; + } else { + v.z = midpoint; + } + } else if (w) { + v.z = w.z + separation(v._, w._); + } + v.parent.A = apportion(v, w, v.parent.A || siblings[0]); + } + + // Computes all real x-coordinates by summing up the modifiers recursively. + function secondWalk(v) { + v._.x = v.z + v.parent.m; + v.m += v.parent.m; + } + + // The core of the algorithm. Here, a new subtree is combined with the + // previous subtrees. Threads are used to traverse the inside and outside + // contours of the left and right subtree up to the highest common level. The + // vertices used for the traversals are vi+, vi-, vo-, and vo+, where the + // superscript o means outside and i means inside, the subscript - means left + // subtree and + means right subtree. For summing up the modifiers along the + // contour, we use respective variables si+, si-, so-, and so+. Whenever two + // nodes of the inside contours conflict, we compute the left one of the + // greatest uncommon ancestors using the function ANCESTOR and call MOVE + // SUBTREE to shift the subtree and prepare the shifts of smaller subtrees. + // Finally, we add a new thread (if necessary). + function apportion(v, w, ancestor) { + if (w) { + var vip = v, + vop = v, + vim = w, + vom = vip.parent.children[0], + sip = vip.m, + sop = vop.m, + sim = vim.m, + som = vom.m, + shift; + while (vim = nextRight(vim), vip = nextLeft(vip), vim && vip) { + vom = nextLeft(vom); + vop = nextRight(vop); + vop.a = v; + shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); + if (shift > 0) { + moveSubtree(nextAncestor(vim, v, ancestor), v, shift); + sip += shift; + sop += shift; + } + sim += vim.m; + sip += vip.m; + som += vom.m; + sop += vop.m; + } + if (vim && !nextRight(vop)) { + vop.t = vim; + vop.m += sim - sop; + } + if (vip && !nextLeft(vom)) { + vom.t = vip; + vom.m += sip - som; + ancestor = v; + } + } + return ancestor; + } + + function sizeNode(node) { + node.x *= dx; + node.y = node.depth * dy; + } + + tree.separation = function(x) { + return arguments.length ? (separation = x, tree) : separation; + }; + + tree.size = function(x) { + return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], tree) : (nodeSize ? null : [dx, dy]); + }; + + tree.nodeSize = function(x) { + return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], tree) : (nodeSize ? [dx, dy] : null); + }; + + return tree; +} + +function treemapSlice(parent, x0, y0, x1, y1) { + var nodes = parent.children, + node, + i = -1, + n = nodes.length, + k = parent.value && (y1 - y0) / parent.value; + + while (++i < n) { + node = nodes[i], node.x0 = x0, node.x1 = x1; + node.y0 = y0, node.y1 = y0 += node.value * k; + } +} + +var phi = (1 + Math.sqrt(5)) / 2; + +function squarifyRatio(ratio, parent, x0, y0, x1, y1) { + var rows = [], + nodes = parent.children, + row, + nodeValue, + i0 = 0, + i1 = 0, + n = nodes.length, + dx, dy, + value = parent.value, + sumValue, + minValue, + maxValue, + newRatio, + minRatio, + alpha, + beta; + + while (i0 < n) { + dx = x1 - x0, dy = y1 - y0; + + // Find the next non-empty node. + do sumValue = nodes[i1++].value; while (!sumValue && i1 < n); + minValue = maxValue = sumValue; + alpha = Math.max(dy / dx, dx / dy) / (value * ratio); + beta = sumValue * sumValue * alpha; + minRatio = Math.max(maxValue / beta, beta / minValue); + + // Keep adding nodes while the aspect ratio maintains or improves. + for (; i1 < n; ++i1) { + sumValue += nodeValue = nodes[i1].value; + if (nodeValue < minValue) minValue = nodeValue; + if (nodeValue > maxValue) maxValue = nodeValue; + beta = sumValue * sumValue * alpha; + newRatio = Math.max(maxValue / beta, beta / minValue); + if (newRatio > minRatio) { sumValue -= nodeValue; break; } + minRatio = newRatio; + } + + // Position and record the row orientation. + rows.push(row = {value: sumValue, dice: dx < dy, children: nodes.slice(i0, i1)}); + if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += dy * sumValue / value : y1); + else treemapSlice(row, x0, y0, value ? x0 += dx * sumValue / value : x1, y1); + value -= sumValue, i0 = i1; + } + + return rows; +} + +var squarify = (function custom(ratio) { + + function squarify(parent, x0, y0, x1, y1) { + squarifyRatio(ratio, parent, x0, y0, x1, y1); + } + + squarify.ratio = function(x) { + return custom((x = +x) > 1 ? x : 1); + }; + + return squarify; +})(phi); + +function index() { + var tile = squarify, + round = false, + dx = 1, + dy = 1, + paddingStack = [0], + paddingInner = constantZero, + paddingTop = constantZero, + paddingRight = constantZero, + paddingBottom = constantZero, + paddingLeft = constantZero; + + function treemap(root) { + root.x0 = + root.y0 = 0; + root.x1 = dx; + root.y1 = dy; + root.eachBefore(positionNode); + paddingStack = [0]; + if (round) root.eachBefore(roundNode); + return root; + } + + function positionNode(node) { + var p = paddingStack[node.depth], + x0 = node.x0 + p, + y0 = node.y0 + p, + x1 = node.x1 - p, + y1 = node.y1 - p; + if (x1 < x0) x0 = x1 = (x0 + x1) / 2; + if (y1 < y0) y0 = y1 = (y0 + y1) / 2; + node.x0 = x0; + node.y0 = y0; + node.x1 = x1; + node.y1 = y1; + if (node.children) { + p = paddingStack[node.depth + 1] = paddingInner(node) / 2; + x0 += paddingLeft(node) - p; + y0 += paddingTop(node) - p; + x1 -= paddingRight(node) - p; + y1 -= paddingBottom(node) - p; + if (x1 < x0) x0 = x1 = (x0 + x1) / 2; + if (y1 < y0) y0 = y1 = (y0 + y1) / 2; + tile(node, x0, y0, x1, y1); + } + } + + treemap.round = function(x) { + return arguments.length ? (round = !!x, treemap) : round; + }; + + treemap.size = function(x) { + return arguments.length ? (dx = +x[0], dy = +x[1], treemap) : [dx, dy]; + }; + + treemap.tile = function(x) { + return arguments.length ? (tile = required(x), treemap) : tile; + }; + + treemap.padding = function(x) { + return arguments.length ? treemap.paddingInner(x).paddingOuter(x) : treemap.paddingInner(); + }; + + treemap.paddingInner = function(x) { + return arguments.length ? (paddingInner = typeof x === "function" ? x : constant$2(+x), treemap) : paddingInner; + }; + + treemap.paddingOuter = function(x) { + return arguments.length ? treemap.paddingTop(x).paddingRight(x).paddingBottom(x).paddingLeft(x) : treemap.paddingTop(); + }; + + treemap.paddingTop = function(x) { + return arguments.length ? (paddingTop = typeof x === "function" ? x : constant$2(+x), treemap) : paddingTop; + }; + + treemap.paddingRight = function(x) { + return arguments.length ? (paddingRight = typeof x === "function" ? x : constant$2(+x), treemap) : paddingRight; + }; + + treemap.paddingBottom = function(x) { + return arguments.length ? (paddingBottom = typeof x === "function" ? x : constant$2(+x), treemap) : paddingBottom; + }; + + treemap.paddingLeft = function(x) { + return arguments.length ? (paddingLeft = typeof x === "function" ? x : constant$2(+x), treemap) : paddingLeft; + }; + + return treemap; +} + +function binary(parent, x0, y0, x1, y1) { + var nodes = parent.children, + i, n = nodes.length, + sum, sums = new Array(n + 1); + + for (sums[0] = sum = i = 0; i < n; ++i) { + sums[i + 1] = sum += nodes[i].value; + } + + partition(0, n, parent.value, x0, y0, x1, y1); + + function partition(i, j, value, x0, y0, x1, y1) { + if (i >= j - 1) { + var node = nodes[i]; + node.x0 = x0, node.y0 = y0; + node.x1 = x1, node.y1 = y1; + return; + } + + var valueOffset = sums[i], + valueTarget = (value / 2) + valueOffset, + k = i + 1, + hi = j - 1; + + while (k < hi) { + var mid = k + hi >>> 1; + if (sums[mid] < valueTarget) k = mid + 1; + else hi = mid; + } + + if ((valueTarget - sums[k - 1]) < (sums[k] - valueTarget) && i + 1 < k) --k; + + var valueLeft = sums[k] - valueOffset, + valueRight = value - valueLeft; + + if ((x1 - x0) > (y1 - y0)) { + var xk = value ? (x0 * valueRight + x1 * valueLeft) / value : x1; + partition(i, k, valueLeft, x0, y0, xk, y1); + partition(k, j, valueRight, xk, y0, x1, y1); + } else { + var yk = value ? (y0 * valueRight + y1 * valueLeft) / value : y1; + partition(i, k, valueLeft, x0, y0, x1, yk); + partition(k, j, valueRight, x0, yk, x1, y1); + } + } +} + +function sliceDice(parent, x0, y0, x1, y1) { + (parent.depth & 1 ? treemapSlice : treemapDice)(parent, x0, y0, x1, y1); +} + +var resquarify = (function custom(ratio) { + + function resquarify(parent, x0, y0, x1, y1) { + if ((rows = parent._squarify) && (rows.ratio === ratio)) { + var rows, + row, + nodes, + i, + j = -1, + n, + m = rows.length, + value = parent.value; + + while (++j < m) { + row = rows[j], nodes = row.children; + for (i = row.value = 0, n = nodes.length; i < n; ++i) row.value += nodes[i].value; + if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += (y1 - y0) * row.value / value : y1); + else treemapSlice(row, x0, y0, value ? x0 += (x1 - x0) * row.value / value : x1, y1); + value -= row.value; + } + } else { + parent._squarify = rows = squarifyRatio(ratio, parent, x0, y0, x1, y1); + rows.ratio = ratio; + } + } + + resquarify.ratio = function(x) { + return custom((x = +x) > 1 ? x : 1); + }; + + return resquarify; +})(phi); + +function area$1(polygon) { + var i = -1, + n = polygon.length, + a, + b = polygon[n - 1], + area = 0; + + while (++i < n) { + a = b; + b = polygon[i]; + area += a[1] * b[0] - a[0] * b[1]; + } + + return area / 2; +} + +function centroid(polygon) { + var i = -1, + n = polygon.length, + x = 0, + y = 0, + a, + b = polygon[n - 1], + c, + k = 0; + + while (++i < n) { + a = b; + b = polygon[i]; + k += c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + + return k *= 3, [x / k, y / k]; +} + +// Returns the 2D cross product of AB and AC vectors, i.e., the z-component of +// the 3D cross product in a quadrant I Cartesian coordinate system (+x is +// right, +y is up). Returns a positive value if ABC is counter-clockwise, +// negative if clockwise, and zero if the points are collinear. +function cross$1(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); +} + +function lexicographicOrder(a, b) { + return a[0] - b[0] || a[1] - b[1]; +} + +// Computes the upper convex hull per the monotone chain algorithm. +// Assumes points.length >= 3, is sorted by x, unique in y. +// Returns an array of indices into points in left-to-right order. +function computeUpperHullIndexes(points) { + const n = points.length, + indexes = [0, 1]; + let size = 2, i; + + for (i = 2; i < n; ++i) { + while (size > 1 && cross$1(points[indexes[size - 2]], points[indexes[size - 1]], points[i]) <= 0) --size; + indexes[size++] = i; + } + + return indexes.slice(0, size); // remove popped points +} + +function hull(points) { + if ((n = points.length) < 3) return null; + + var i, + n, + sortedPoints = new Array(n), + flippedPoints = new Array(n); + + for (i = 0; i < n; ++i) sortedPoints[i] = [+points[i][0], +points[i][1], i]; + sortedPoints.sort(lexicographicOrder); + for (i = 0; i < n; ++i) flippedPoints[i] = [sortedPoints[i][0], -sortedPoints[i][1]]; + + var upperIndexes = computeUpperHullIndexes(sortedPoints), + lowerIndexes = computeUpperHullIndexes(flippedPoints); + + // Construct the hull polygon, removing possible duplicate endpoints. + var skipLeft = lowerIndexes[0] === upperIndexes[0], + skipRight = lowerIndexes[lowerIndexes.length - 1] === upperIndexes[upperIndexes.length - 1], + hull = []; + + // Add upper hull in right-to-l order. + // Then add lower hull in left-to-right order. + for (i = upperIndexes.length - 1; i >= 0; --i) hull.push(points[sortedPoints[upperIndexes[i]][2]]); + for (i = +skipLeft; i < lowerIndexes.length - skipRight; ++i) hull.push(points[sortedPoints[lowerIndexes[i]][2]]); + + return hull; +} + +function contains(polygon, point) { + var n = polygon.length, + p = polygon[n - 1], + x = point[0], y = point[1], + x0 = p[0], y0 = p[1], + x1, y1, + inside = false; + + for (var i = 0; i < n; ++i) { + p = polygon[i], x1 = p[0], y1 = p[1]; + if (((y1 > y) !== (y0 > y)) && (x < (x0 - x1) * (y - y1) / (y0 - y1) + x1)) inside = !inside; + x0 = x1, y0 = y1; + } + + return inside; +} + +function length(polygon) { + var i = -1, + n = polygon.length, + b = polygon[n - 1], + xa, + ya, + xb = b[0], + yb = b[1], + perimeter = 0; + + while (++i < n) { + xa = xb; + ya = yb; + b = polygon[i]; + xb = b[0]; + yb = b[1]; + xa -= xb; + ya -= yb; + perimeter += Math.hypot(xa, ya); + } + + return perimeter; +} + +var defaultSource = Math.random; + +var uniform = (function sourceRandomUniform(source) { + function randomUniform(min, max) { + min = min == null ? 0 : +min; + max = max == null ? 1 : +max; + if (arguments.length === 1) max = min, min = 0; + else max -= min; + return function() { + return source() * max + min; + }; + } + + randomUniform.source = sourceRandomUniform; + + return randomUniform; +})(defaultSource); + +var int = (function sourceRandomInt(source) { + function randomInt(min, max) { + if (arguments.length < 2) max = min, min = 0; + min = Math.floor(min); + max = Math.floor(max) - min; + return function() { + return Math.floor(source() * max + min); + }; + } + + randomInt.source = sourceRandomInt; + + return randomInt; +})(defaultSource); + +var normal = (function sourceRandomNormal(source) { + function randomNormal(mu, sigma) { + var x, r; + mu = mu == null ? 0 : +mu; + sigma = sigma == null ? 1 : +sigma; + return function() { + var y; + + // If available, use the second previously-generated uniform random. + if (x != null) y = x, x = null; + + // Otherwise, generate a new x and y. + else do { + x = source() * 2 - 1; + y = source() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + + return mu + sigma * y * Math.sqrt(-2 * Math.log(r) / r); + }; + } + + randomNormal.source = sourceRandomNormal; + + return randomNormal; +})(defaultSource); + +var logNormal = (function sourceRandomLogNormal(source) { + var N = normal.source(source); + + function randomLogNormal() { + var randomNormal = N.apply(this, arguments); + return function() { + return Math.exp(randomNormal()); + }; + } + + randomLogNormal.source = sourceRandomLogNormal; + + return randomLogNormal; +})(defaultSource); + +var irwinHall = (function sourceRandomIrwinHall(source) { + function randomIrwinHall(n) { + if ((n = +n) <= 0) return () => 0; + return function() { + for (var sum = 0, i = n; i > 1; --i) sum += source(); + return sum + i * source(); + }; + } + + randomIrwinHall.source = sourceRandomIrwinHall; + + return randomIrwinHall; +})(defaultSource); + +var bates = (function sourceRandomBates(source) { + var I = irwinHall.source(source); + + function randomBates(n) { + // use limiting distribution at n === 0 + if ((n = +n) === 0) return source; + var randomIrwinHall = I(n); + return function() { + return randomIrwinHall() / n; + }; + } + + randomBates.source = sourceRandomBates; + + return randomBates; +})(defaultSource); + +var exponential = (function sourceRandomExponential(source) { + function randomExponential(lambda) { + return function() { + return -Math.log1p(-source()) / lambda; + }; + } + + randomExponential.source = sourceRandomExponential; + + return randomExponential; +})(defaultSource); + +var pareto = (function sourceRandomPareto(source) { + function randomPareto(alpha) { + if ((alpha = +alpha) < 0) throw new RangeError("invalid alpha"); + alpha = 1 / -alpha; + return function() { + return Math.pow(1 - source(), alpha); + }; + } + + randomPareto.source = sourceRandomPareto; + + return randomPareto; +})(defaultSource); + +var bernoulli = (function sourceRandomBernoulli(source) { + function randomBernoulli(p) { + if ((p = +p) < 0 || p > 1) throw new RangeError("invalid p"); + return function() { + return Math.floor(source() + p); + }; + } + + randomBernoulli.source = sourceRandomBernoulli; + + return randomBernoulli; +})(defaultSource); + +var geometric = (function sourceRandomGeometric(source) { + function randomGeometric(p) { + if ((p = +p) < 0 || p > 1) throw new RangeError("invalid p"); + if (p === 0) return () => Infinity; + if (p === 1) return () => 1; + p = Math.log1p(-p); + return function() { + return 1 + Math.floor(Math.log1p(-source()) / p); + }; + } + + randomGeometric.source = sourceRandomGeometric; + + return randomGeometric; +})(defaultSource); + +var gamma = (function sourceRandomGamma(source) { + var randomNormal = normal.source(source)(); + + function randomGamma(k, theta) { + if ((k = +k) < 0) throw new RangeError("invalid k"); + // degenerate distribution if k === 0 + if (k === 0) return () => 0; + theta = theta == null ? 1 : +theta; + // exponential distribution if k === 1 + if (k === 1) return () => -Math.log1p(-source()) * theta; + + var d = (k < 1 ? k + 1 : k) - 1 / 3, + c = 1 / (3 * Math.sqrt(d)), + multiplier = k < 1 ? () => Math.pow(source(), 1 / k) : () => 1; + return function() { + do { + do { + var x = randomNormal(), + v = 1 + c * x; + } while (v <= 0); + v *= v * v; + var u = 1 - source(); + } while (u >= 1 - 0.0331 * x * x * x * x && Math.log(u) >= 0.5 * x * x + d * (1 - v + Math.log(v))); + return d * v * multiplier() * theta; + }; + } + + randomGamma.source = sourceRandomGamma; + + return randomGamma; +})(defaultSource); + +var beta = (function sourceRandomBeta(source) { + var G = gamma.source(source); + + function randomBeta(alpha, beta) { + var X = G(alpha), + Y = G(beta); + return function() { + var x = X(); + return x === 0 ? 0 : x / (x + Y()); + }; + } + + randomBeta.source = sourceRandomBeta; + + return randomBeta; +})(defaultSource); + +var binomial = (function sourceRandomBinomial(source) { + var G = geometric.source(source), + B = beta.source(source); + + function randomBinomial(n, p) { + n = +n; + if ((p = +p) >= 1) return () => n; + if (p <= 0) return () => 0; + return function() { + var acc = 0, nn = n, pp = p; + while (nn * pp > 16 && nn * (1 - pp) > 16) { + var i = Math.floor((nn + 1) * pp), + y = B(i, nn - i + 1)(); + if (y <= pp) { + acc += i; + nn -= i; + pp = (pp - y) / (1 - y); + } else { + nn = i - 1; + pp /= y; + } + } + var sign = pp < 0.5, + pFinal = sign ? pp : 1 - pp, + g = G(pFinal); + for (var s = g(), k = 0; s <= nn; ++k) s += g(); + return acc + (sign ? k : nn - k); + }; + } + + randomBinomial.source = sourceRandomBinomial; + + return randomBinomial; +})(defaultSource); + +var weibull = (function sourceRandomWeibull(source) { + function randomWeibull(k, a, b) { + var outerFunc; + if ((k = +k) === 0) { + outerFunc = x => -Math.log(x); + } else { + k = 1 / k; + outerFunc = x => Math.pow(x, k); + } + a = a == null ? 0 : +a; + b = b == null ? 1 : +b; + return function() { + return a + b * outerFunc(-Math.log1p(-source())); + }; + } + + randomWeibull.source = sourceRandomWeibull; + + return randomWeibull; +})(defaultSource); + +var cauchy = (function sourceRandomCauchy(source) { + function randomCauchy(a, b) { + a = a == null ? 0 : +a; + b = b == null ? 1 : +b; + return function() { + return a + b * Math.tan(Math.PI * source()); + }; + } + + randomCauchy.source = sourceRandomCauchy; + + return randomCauchy; +})(defaultSource); + +var logistic = (function sourceRandomLogistic(source) { + function randomLogistic(a, b) { + a = a == null ? 0 : +a; + b = b == null ? 1 : +b; + return function() { + var u = source(); + return a + b * Math.log(u / (1 - u)); + }; + } + + randomLogistic.source = sourceRandomLogistic; + + return randomLogistic; +})(defaultSource); + +var poisson = (function sourceRandomPoisson(source) { + var G = gamma.source(source), + B = binomial.source(source); + + function randomPoisson(lambda) { + return function() { + var acc = 0, l = lambda; + while (l > 16) { + var n = Math.floor(0.875 * l), + t = G(n)(); + if (t > l) return acc + B(n - 1, l / t)(); + acc += n; + l -= t; + } + for (var s = -Math.log1p(-source()), k = 0; s <= l; ++k) s -= Math.log1p(-source()); + return acc + k; + }; + } + + randomPoisson.source = sourceRandomPoisson; + + return randomPoisson; +})(defaultSource); + +// https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use +const mul = 0x19660D; +const inc = 0x3C6EF35F; +const eps = 1 / 0x100000000; + +function lcg(seed = Math.random()) { + let state = (0 <= seed && seed < 1 ? seed / eps : Math.abs(seed)) | 0; + return () => (state = mul * state + inc | 0, eps * (state >>> 0)); +} + +function initRange(domain, range) { + switch (arguments.length) { + case 0: break; + case 1: this.range(domain); break; + default: this.range(range).domain(domain); break; + } + return this; +} + +function initInterpolator(domain, interpolator) { + switch (arguments.length) { + case 0: break; + case 1: { + if (typeof domain === "function") this.interpolator(domain); + else this.range(domain); + break; + } + default: { + this.domain(domain); + if (typeof interpolator === "function") this.interpolator(interpolator); + else this.range(interpolator); + break; + } + } + return this; +} + +const implicit = Symbol("implicit"); + +function ordinal() { + var index = new InternMap(), + domain = [], + range = [], + unknown = implicit; + + function scale(d) { + let i = index.get(d); + if (i === undefined) { + if (unknown !== implicit) return unknown; + index.set(d, i = domain.push(d) - 1); + } + return range[i % range.length]; + } + + scale.domain = function(_) { + if (!arguments.length) return domain.slice(); + domain = [], index = new InternMap(); + for (const value of _) { + if (index.has(value)) continue; + index.set(value, domain.push(value) - 1); + } + return scale; + }; + + scale.range = function(_) { + return arguments.length ? (range = Array.from(_), scale) : range.slice(); + }; + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + scale.copy = function() { + return ordinal(domain, range).unknown(unknown); + }; + + initRange.apply(scale, arguments); + + return scale; +} + +function band() { + var scale = ordinal().unknown(undefined), + domain = scale.domain, + ordinalRange = scale.range, + r0 = 0, + r1 = 1, + step, + bandwidth, + round = false, + paddingInner = 0, + paddingOuter = 0, + align = 0.5; + + delete scale.unknown; + + function rescale() { + var n = domain().length, + reverse = r1 < r0, + start = reverse ? r1 : r0, + stop = reverse ? r0 : r1; + step = (stop - start) / Math.max(1, n - paddingInner + paddingOuter * 2); + if (round) step = Math.floor(step); + start += (stop - start - step * (n - paddingInner)) * align; + bandwidth = step * (1 - paddingInner); + if (round) start = Math.round(start), bandwidth = Math.round(bandwidth); + var values = range$2(n).map(function(i) { return start + step * i; }); + return ordinalRange(reverse ? values.reverse() : values); + } + + scale.domain = function(_) { + return arguments.length ? (domain(_), rescale()) : domain(); + }; + + scale.range = function(_) { + return arguments.length ? ([r0, r1] = _, r0 = +r0, r1 = +r1, rescale()) : [r0, r1]; + }; + + scale.rangeRound = function(_) { + return [r0, r1] = _, r0 = +r0, r1 = +r1, round = true, rescale(); + }; + + scale.bandwidth = function() { + return bandwidth; + }; + + scale.step = function() { + return step; + }; + + scale.round = function(_) { + return arguments.length ? (round = !!_, rescale()) : round; + }; + + scale.padding = function(_) { + return arguments.length ? (paddingInner = Math.min(1, paddingOuter = +_), rescale()) : paddingInner; + }; + + scale.paddingInner = function(_) { + return arguments.length ? (paddingInner = Math.min(1, _), rescale()) : paddingInner; + }; + + scale.paddingOuter = function(_) { + return arguments.length ? (paddingOuter = +_, rescale()) : paddingOuter; + }; + + scale.align = function(_) { + return arguments.length ? (align = Math.max(0, Math.min(1, _)), rescale()) : align; + }; + + scale.copy = function() { + return band(domain(), [r0, r1]) + .round(round) + .paddingInner(paddingInner) + .paddingOuter(paddingOuter) + .align(align); + }; + + return initRange.apply(rescale(), arguments); +} + +function pointish(scale) { + var copy = scale.copy; + + scale.padding = scale.paddingOuter; + delete scale.paddingInner; + delete scale.paddingOuter; + + scale.copy = function() { + return pointish(copy()); + }; + + return scale; +} + +function point$4() { + return pointish(band.apply(null, arguments).paddingInner(1)); +} + +function constants(x) { + return function() { + return x; + }; +} + +function number$1(x) { + return +x; +} + +var unit = [0, 1]; + +function identity$3(x) { + return x; +} + +function normalize(a, b) { + return (b -= (a = +a)) + ? function(x) { return (x - a) / b; } + : constants(isNaN(b) ? NaN : 0.5); +} + +function clamper(a, b) { + var t; + if (a > b) t = a, a = b, b = t; + return function(x) { return Math.max(a, Math.min(b, x)); }; +} + +// normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. +// interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b]. +function bimap(domain, range, interpolate) { + var d0 = domain[0], d1 = domain[1], r0 = range[0], r1 = range[1]; + if (d1 < d0) d0 = normalize(d1, d0), r0 = interpolate(r1, r0); + else d0 = normalize(d0, d1), r0 = interpolate(r0, r1); + return function(x) { return r0(d0(x)); }; +} + +function polymap(domain, range, interpolate) { + var j = Math.min(domain.length, range.length) - 1, + d = new Array(j), + r = new Array(j), + i = -1; + + // Reverse descending domains. + if (domain[j] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + + while (++i < j) { + d[i] = normalize(domain[i], domain[i + 1]); + r[i] = interpolate(range[i], range[i + 1]); + } + + return function(x) { + var i = bisect(domain, x, 1, j) - 1; + return r[i](d[i](x)); + }; +} + +function copy$1(source, target) { + return target + .domain(source.domain()) + .range(source.range()) + .interpolate(source.interpolate()) + .clamp(source.clamp()) + .unknown(source.unknown()); +} + +function transformer$2() { + var domain = unit, + range = unit, + interpolate = interpolate$2, + transform, + untransform, + unknown, + clamp = identity$3, + piecewise, + output, + input; + + function rescale() { + var n = Math.min(domain.length, range.length); + if (clamp !== identity$3) clamp = clamper(domain[0], domain[n - 1]); + piecewise = n > 2 ? polymap : bimap; + output = input = null; + return scale; + } + + function scale(x) { + return x == null || isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate)))(transform(clamp(x))); + } + + scale.invert = function(y) { + return clamp(untransform((input || (input = piecewise(range, domain.map(transform), interpolateNumber)))(y))); + }; + + scale.domain = function(_) { + return arguments.length ? (domain = Array.from(_, number$1), rescale()) : domain.slice(); + }; + + scale.range = function(_) { + return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); + }; + + scale.rangeRound = function(_) { + return range = Array.from(_), interpolate = interpolateRound, rescale(); + }; + + scale.clamp = function(_) { + return arguments.length ? (clamp = _ ? true : identity$3, rescale()) : clamp !== identity$3; + }; + + scale.interpolate = function(_) { + return arguments.length ? (interpolate = _, rescale()) : interpolate; + }; + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + return function(t, u) { + transform = t, untransform = u; + return rescale(); + }; +} + +function continuous() { + return transformer$2()(identity$3, identity$3); +} + +function tickFormat(start, stop, count, specifier) { + var step = tickStep(start, stop, count), + precision; + specifier = formatSpecifier(specifier == null ? ",f" : specifier); + switch (specifier.type) { + case "s": { + var value = Math.max(Math.abs(start), Math.abs(stop)); + if (specifier.precision == null && !isNaN(precision = precisionPrefix(step, value))) specifier.precision = precision; + return exports.formatPrefix(specifier, value); + } + case "": + case "e": + case "g": + case "p": + case "r": { + if (specifier.precision == null && !isNaN(precision = precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e"); + break; + } + case "f": + case "%": { + if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2; + break; + } + } + return exports.format(specifier); +} + +function linearish(scale) { + var domain = scale.domain; + + scale.ticks = function(count) { + var d = domain(); + return ticks(d[0], d[d.length - 1], count == null ? 10 : count); + }; + + scale.tickFormat = function(count, specifier) { + var d = domain(); + return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier); + }; + + scale.nice = function(count) { + if (count == null) count = 10; + + var d = domain(); + var i0 = 0; + var i1 = d.length - 1; + var start = d[i0]; + var stop = d[i1]; + var prestep; + var step; + var maxIter = 10; + + if (stop < start) { + step = start, start = stop, stop = step; + step = i0, i0 = i1, i1 = step; + } + + while (maxIter-- > 0) { + step = tickIncrement(start, stop, count); + if (step === prestep) { + d[i0] = start; + d[i1] = stop; + return domain(d); + } else if (step > 0) { + start = Math.floor(start / step) * step; + stop = Math.ceil(stop / step) * step; + } else if (step < 0) { + start = Math.ceil(start * step) / step; + stop = Math.floor(stop * step) / step; + } else { + break; + } + prestep = step; + } + + return scale; + }; + + return scale; +} + +function linear() { + var scale = continuous(); + + scale.copy = function() { + return copy$1(scale, linear()); + }; + + initRange.apply(scale, arguments); + + return linearish(scale); +} + +function identity$2(domain) { + var unknown; + + function scale(x) { + return x == null || isNaN(x = +x) ? unknown : x; + } + + scale.invert = scale; + + scale.domain = scale.range = function(_) { + return arguments.length ? (domain = Array.from(_, number$1), scale) : domain.slice(); + }; + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + scale.copy = function() { + return identity$2(domain).unknown(unknown); + }; + + domain = arguments.length ? Array.from(domain, number$1) : [0, 1]; + + return linearish(scale); +} + +function nice(domain, interval) { + domain = domain.slice(); + + var i0 = 0, + i1 = domain.length - 1, + x0 = domain[i0], + x1 = domain[i1], + t; + + if (x1 < x0) { + t = i0, i0 = i1, i1 = t; + t = x0, x0 = x1, x1 = t; + } + + domain[i0] = interval.floor(x0); + domain[i1] = interval.ceil(x1); + return domain; +} + +function transformLog(x) { + return Math.log(x); +} + +function transformExp(x) { + return Math.exp(x); +} + +function transformLogn(x) { + return -Math.log(-x); +} + +function transformExpn(x) { + return -Math.exp(-x); +} + +function pow10(x) { + return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x; +} + +function powp(base) { + return base === 10 ? pow10 + : base === Math.E ? Math.exp + : x => Math.pow(base, x); +} + +function logp(base) { + return base === Math.E ? Math.log + : base === 10 && Math.log10 + || base === 2 && Math.log2 + || (base = Math.log(base), x => Math.log(x) / base); +} + +function reflect(f) { + return (x, k) => -f(-x, k); +} + +function loggish(transform) { + const scale = transform(transformLog, transformExp); + const domain = scale.domain; + let base = 10; + let logs; + let pows; + + function rescale() { + logs = logp(base), pows = powp(base); + if (domain()[0] < 0) { + logs = reflect(logs), pows = reflect(pows); + transform(transformLogn, transformExpn); + } else { + transform(transformLog, transformExp); + } + return scale; + } + + scale.base = function(_) { + return arguments.length ? (base = +_, rescale()) : base; + }; + + scale.domain = function(_) { + return arguments.length ? (domain(_), rescale()) : domain(); + }; + + scale.ticks = count => { + const d = domain(); + let u = d[0]; + let v = d[d.length - 1]; + const r = v < u; + + if (r) ([u, v] = [v, u]); + + let i = logs(u); + let j = logs(v); + let k; + let t; + const n = count == null ? 10 : +count; + let z = []; + + if (!(base % 1) && j - i < n) { + i = Math.floor(i), j = Math.ceil(j); + if (u > 0) for (; i <= j; ++i) { + for (k = 1; k < base; ++k) { + t = i < 0 ? k / pows(-i) : k * pows(i); + if (t < u) continue; + if (t > v) break; + z.push(t); + } + } else for (; i <= j; ++i) { + for (k = base - 1; k >= 1; --k) { + t = i > 0 ? k / pows(-i) : k * pows(i); + if (t < u) continue; + if (t > v) break; + z.push(t); + } + } + if (z.length * 2 < n) z = ticks(u, v, n); + } else { + z = ticks(i, j, Math.min(j - i, n)).map(pows); + } + return r ? z.reverse() : z; + }; + + scale.tickFormat = (count, specifier) => { + if (count == null) count = 10; + if (specifier == null) specifier = base === 10 ? "s" : ","; + if (typeof specifier !== "function") { + if (!(base % 1) && (specifier = formatSpecifier(specifier)).precision == null) specifier.trim = true; + specifier = exports.format(specifier); + } + if (count === Infinity) return specifier; + const k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate? + return d => { + let i = d / pows(Math.round(logs(d))); + if (i * base < base - 0.5) i *= base; + return i <= k ? specifier(d) : ""; + }; + }; + + scale.nice = () => { + return domain(nice(domain(), { + floor: x => pows(Math.floor(logs(x))), + ceil: x => pows(Math.ceil(logs(x))) + })); + }; + + return scale; +} + +function log() { + const scale = loggish(transformer$2()).domain([1, 10]); + scale.copy = () => copy$1(scale, log()).base(scale.base()); + initRange.apply(scale, arguments); + return scale; +} + +function transformSymlog(c) { + return function(x) { + return Math.sign(x) * Math.log1p(Math.abs(x / c)); + }; +} + +function transformSymexp(c) { + return function(x) { + return Math.sign(x) * Math.expm1(Math.abs(x)) * c; + }; +} + +function symlogish(transform) { + var c = 1, scale = transform(transformSymlog(c), transformSymexp(c)); + + scale.constant = function(_) { + return arguments.length ? transform(transformSymlog(c = +_), transformSymexp(c)) : c; + }; + + return linearish(scale); +} + +function symlog() { + var scale = symlogish(transformer$2()); + + scale.copy = function() { + return copy$1(scale, symlog()).constant(scale.constant()); + }; + + return initRange.apply(scale, arguments); +} + +function transformPow(exponent) { + return function(x) { + return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent); + }; +} + +function transformSqrt(x) { + return x < 0 ? -Math.sqrt(-x) : Math.sqrt(x); +} + +function transformSquare(x) { + return x < 0 ? -x * x : x * x; +} + +function powish(transform) { + var scale = transform(identity$3, identity$3), + exponent = 1; + + function rescale() { + return exponent === 1 ? transform(identity$3, identity$3) + : exponent === 0.5 ? transform(transformSqrt, transformSquare) + : transform(transformPow(exponent), transformPow(1 / exponent)); + } + + scale.exponent = function(_) { + return arguments.length ? (exponent = +_, rescale()) : exponent; + }; + + return linearish(scale); +} + +function pow() { + var scale = powish(transformer$2()); + + scale.copy = function() { + return copy$1(scale, pow()).exponent(scale.exponent()); + }; + + initRange.apply(scale, arguments); + + return scale; +} + +function sqrt$1() { + return pow.apply(null, arguments).exponent(0.5); +} + +function square$1(x) { + return Math.sign(x) * x * x; +} + +function unsquare(x) { + return Math.sign(x) * Math.sqrt(Math.abs(x)); +} + +function radial() { + var squared = continuous(), + range = [0, 1], + round = false, + unknown; + + function scale(x) { + var y = unsquare(squared(x)); + return isNaN(y) ? unknown : round ? Math.round(y) : y; + } + + scale.invert = function(y) { + return squared.invert(square$1(y)); + }; + + scale.domain = function(_) { + return arguments.length ? (squared.domain(_), scale) : squared.domain(); + }; + + scale.range = function(_) { + return arguments.length ? (squared.range((range = Array.from(_, number$1)).map(square$1)), scale) : range.slice(); + }; + + scale.rangeRound = function(_) { + return scale.range(_).round(true); + }; + + scale.round = function(_) { + return arguments.length ? (round = !!_, scale) : round; + }; + + scale.clamp = function(_) { + return arguments.length ? (squared.clamp(_), scale) : squared.clamp(); + }; + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + scale.copy = function() { + return radial(squared.domain(), range) + .round(round) + .clamp(squared.clamp()) + .unknown(unknown); + }; + + initRange.apply(scale, arguments); + + return linearish(scale); +} + +function quantile() { + var domain = [], + range = [], + thresholds = [], + unknown; + + function rescale() { + var i = 0, n = Math.max(1, range.length); + thresholds = new Array(n - 1); + while (++i < n) thresholds[i - 1] = quantileSorted(domain, i / n); + return scale; + } + + function scale(x) { + return x == null || isNaN(x = +x) ? unknown : range[bisect(thresholds, x)]; + } + + scale.invertExtent = function(y) { + var i = range.indexOf(y); + return i < 0 ? [NaN, NaN] : [ + i > 0 ? thresholds[i - 1] : domain[0], + i < thresholds.length ? thresholds[i] : domain[domain.length - 1] + ]; + }; + + scale.domain = function(_) { + if (!arguments.length) return domain.slice(); + domain = []; + for (let d of _) if (d != null && !isNaN(d = +d)) domain.push(d); + domain.sort(ascending$3); + return rescale(); + }; + + scale.range = function(_) { + return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); + }; + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + scale.quantiles = function() { + return thresholds.slice(); + }; + + scale.copy = function() { + return quantile() + .domain(domain) + .range(range) + .unknown(unknown); + }; + + return initRange.apply(scale, arguments); +} + +function quantize() { + var x0 = 0, + x1 = 1, + n = 1, + domain = [0.5], + range = [0, 1], + unknown; + + function scale(x) { + return x != null && x <= x ? range[bisect(domain, x, 0, n)] : unknown; + } + + function rescale() { + var i = -1; + domain = new Array(n); + while (++i < n) domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1); + return scale; + } + + scale.domain = function(_) { + return arguments.length ? ([x0, x1] = _, x0 = +x0, x1 = +x1, rescale()) : [x0, x1]; + }; + + scale.range = function(_) { + return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice(); + }; + + scale.invertExtent = function(y) { + var i = range.indexOf(y); + return i < 0 ? [NaN, NaN] + : i < 1 ? [x0, domain[0]] + : i >= n ? [domain[n - 1], x1] + : [domain[i - 1], domain[i]]; + }; + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : scale; + }; + + scale.thresholds = function() { + return domain.slice(); + }; + + scale.copy = function() { + return quantize() + .domain([x0, x1]) + .range(range) + .unknown(unknown); + }; + + return initRange.apply(linearish(scale), arguments); +} + +function threshold() { + var domain = [0.5], + range = [0, 1], + unknown, + n = 1; + + function scale(x) { + return x != null && x <= x ? range[bisect(domain, x, 0, n)] : unknown; + } + + scale.domain = function(_) { + return arguments.length ? (domain = Array.from(_), n = Math.min(domain.length, range.length - 1), scale) : domain.slice(); + }; + + scale.range = function(_) { + return arguments.length ? (range = Array.from(_), n = Math.min(domain.length, range.length - 1), scale) : range.slice(); + }; + + scale.invertExtent = function(y) { + var i = range.indexOf(y); + return [domain[i - 1], domain[i]]; + }; + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + scale.copy = function() { + return threshold() + .domain(domain) + .range(range) + .unknown(unknown); + }; + + return initRange.apply(scale, arguments); +} + +const t0 = new Date, t1 = new Date; + +function timeInterval(floori, offseti, count, field) { + + function interval(date) { + return floori(date = arguments.length === 0 ? new Date : new Date(+date)), date; + } + + interval.floor = (date) => { + return floori(date = new Date(+date)), date; + }; + + interval.ceil = (date) => { + return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date; + }; + + interval.round = (date) => { + const d0 = interval(date), d1 = interval.ceil(date); + return date - d0 < d1 - date ? d0 : d1; + }; + + interval.offset = (date, step) => { + return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date; + }; + + interval.range = (start, stop, step) => { + const range = []; + start = interval.ceil(start); + step = step == null ? 1 : Math.floor(step); + if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date + let previous; + do range.push(previous = new Date(+start)), offseti(start, step), floori(start); + while (previous < start && start < stop); + return range; + }; + + interval.filter = (test) => { + return timeInterval((date) => { + if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1); + }, (date, step) => { + if (date >= date) { + if (step < 0) while (++step <= 0) { + while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty + } else while (--step >= 0) { + while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty + } + } + }); + }; + + if (count) { + interval.count = (start, end) => { + t0.setTime(+start), t1.setTime(+end); + floori(t0), floori(t1); + return Math.floor(count(t0, t1)); + }; + + interval.every = (step) => { + step = Math.floor(step); + return !isFinite(step) || !(step > 0) ? null + : !(step > 1) ? interval + : interval.filter(field + ? (d) => field(d) % step === 0 + : (d) => interval.count(0, d) % step === 0); + }; + } + + return interval; +} + +const millisecond = timeInterval(() => { + // noop +}, (date, step) => { + date.setTime(+date + step); +}, (start, end) => { + return end - start; +}); + +// An optimized implementation for this simple case. +millisecond.every = (k) => { + k = Math.floor(k); + if (!isFinite(k) || !(k > 0)) return null; + if (!(k > 1)) return millisecond; + return timeInterval((date) => { + date.setTime(Math.floor(date / k) * k); + }, (date, step) => { + date.setTime(+date + step * k); + }, (start, end) => { + return (end - start) / k; + }); +}; + +const milliseconds = millisecond.range; + +const durationSecond = 1000; +const durationMinute = durationSecond * 60; +const durationHour = durationMinute * 60; +const durationDay = durationHour * 24; +const durationWeek = durationDay * 7; +const durationMonth = durationDay * 30; +const durationYear = durationDay * 365; + +const second = timeInterval((date) => { + date.setTime(date - date.getMilliseconds()); +}, (date, step) => { + date.setTime(+date + step * durationSecond); +}, (start, end) => { + return (end - start) / durationSecond; +}, (date) => { + return date.getUTCSeconds(); +}); + +const seconds = second.range; + +const timeMinute = timeInterval((date) => { + date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond); +}, (date, step) => { + date.setTime(+date + step * durationMinute); +}, (start, end) => { + return (end - start) / durationMinute; +}, (date) => { + return date.getMinutes(); +}); + +const timeMinutes = timeMinute.range; + +const utcMinute = timeInterval((date) => { + date.setUTCSeconds(0, 0); +}, (date, step) => { + date.setTime(+date + step * durationMinute); +}, (start, end) => { + return (end - start) / durationMinute; +}, (date) => { + return date.getUTCMinutes(); +}); + +const utcMinutes = utcMinute.range; + +const timeHour = timeInterval((date) => { + date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute); +}, (date, step) => { + date.setTime(+date + step * durationHour); +}, (start, end) => { + return (end - start) / durationHour; +}, (date) => { + return date.getHours(); +}); + +const timeHours = timeHour.range; + +const utcHour = timeInterval((date) => { + date.setUTCMinutes(0, 0, 0); +}, (date, step) => { + date.setTime(+date + step * durationHour); +}, (start, end) => { + return (end - start) / durationHour; +}, (date) => { + return date.getUTCHours(); +}); + +const utcHours = utcHour.range; + +const timeDay = timeInterval( + date => date.setHours(0, 0, 0, 0), + (date, step) => date.setDate(date.getDate() + step), + (start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay, + date => date.getDate() - 1 +); + +const timeDays = timeDay.range; + +const utcDay = timeInterval((date) => { + date.setUTCHours(0, 0, 0, 0); +}, (date, step) => { + date.setUTCDate(date.getUTCDate() + step); +}, (start, end) => { + return (end - start) / durationDay; +}, (date) => { + return date.getUTCDate() - 1; +}); + +const utcDays = utcDay.range; + +const unixDay = timeInterval((date) => { + date.setUTCHours(0, 0, 0, 0); +}, (date, step) => { + date.setUTCDate(date.getUTCDate() + step); +}, (start, end) => { + return (end - start) / durationDay; +}, (date) => { + return Math.floor(date / durationDay); +}); + +const unixDays = unixDay.range; + +function timeWeekday(i) { + return timeInterval((date) => { + date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); + date.setHours(0, 0, 0, 0); + }, (date, step) => { + date.setDate(date.getDate() + step * 7); + }, (start, end) => { + return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek; + }); +} + +const timeSunday = timeWeekday(0); +const timeMonday = timeWeekday(1); +const timeTuesday = timeWeekday(2); +const timeWednesday = timeWeekday(3); +const timeThursday = timeWeekday(4); +const timeFriday = timeWeekday(5); +const timeSaturday = timeWeekday(6); + +const timeSundays = timeSunday.range; +const timeMondays = timeMonday.range; +const timeTuesdays = timeTuesday.range; +const timeWednesdays = timeWednesday.range; +const timeThursdays = timeThursday.range; +const timeFridays = timeFriday.range; +const timeSaturdays = timeSaturday.range; + +function utcWeekday(i) { + return timeInterval((date) => { + date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); + date.setUTCHours(0, 0, 0, 0); + }, (date, step) => { + date.setUTCDate(date.getUTCDate() + step * 7); + }, (start, end) => { + return (end - start) / durationWeek; + }); +} + +const utcSunday = utcWeekday(0); +const utcMonday = utcWeekday(1); +const utcTuesday = utcWeekday(2); +const utcWednesday = utcWeekday(3); +const utcThursday = utcWeekday(4); +const utcFriday = utcWeekday(5); +const utcSaturday = utcWeekday(6); + +const utcSundays = utcSunday.range; +const utcMondays = utcMonday.range; +const utcTuesdays = utcTuesday.range; +const utcWednesdays = utcWednesday.range; +const utcThursdays = utcThursday.range; +const utcFridays = utcFriday.range; +const utcSaturdays = utcSaturday.range; + +const timeMonth = timeInterval((date) => { + date.setDate(1); + date.setHours(0, 0, 0, 0); +}, (date, step) => { + date.setMonth(date.getMonth() + step); +}, (start, end) => { + return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; +}, (date) => { + return date.getMonth(); +}); + +const timeMonths = timeMonth.range; + +const utcMonth = timeInterval((date) => { + date.setUTCDate(1); + date.setUTCHours(0, 0, 0, 0); +}, (date, step) => { + date.setUTCMonth(date.getUTCMonth() + step); +}, (start, end) => { + return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12; +}, (date) => { + return date.getUTCMonth(); +}); + +const utcMonths = utcMonth.range; + +const timeYear = timeInterval((date) => { + date.setMonth(0, 1); + date.setHours(0, 0, 0, 0); +}, (date, step) => { + date.setFullYear(date.getFullYear() + step); +}, (start, end) => { + return end.getFullYear() - start.getFullYear(); +}, (date) => { + return date.getFullYear(); +}); + +// An optimized implementation for this simple case. +timeYear.every = (k) => { + return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { + date.setFullYear(Math.floor(date.getFullYear() / k) * k); + date.setMonth(0, 1); + date.setHours(0, 0, 0, 0); + }, (date, step) => { + date.setFullYear(date.getFullYear() + step * k); + }); +}; + +const timeYears = timeYear.range; + +const utcYear = timeInterval((date) => { + date.setUTCMonth(0, 1); + date.setUTCHours(0, 0, 0, 0); +}, (date, step) => { + date.setUTCFullYear(date.getUTCFullYear() + step); +}, (start, end) => { + return end.getUTCFullYear() - start.getUTCFullYear(); +}, (date) => { + return date.getUTCFullYear(); +}); + +// An optimized implementation for this simple case. +utcYear.every = (k) => { + return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { + date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k); + date.setUTCMonth(0, 1); + date.setUTCHours(0, 0, 0, 0); + }, (date, step) => { + date.setUTCFullYear(date.getUTCFullYear() + step * k); + }); +}; + +const utcYears = utcYear.range; + +function ticker(year, month, week, day, hour, minute) { + + const tickIntervals = [ + [second, 1, durationSecond], + [second, 5, 5 * durationSecond], + [second, 15, 15 * durationSecond], + [second, 30, 30 * durationSecond], + [minute, 1, durationMinute], + [minute, 5, 5 * durationMinute], + [minute, 15, 15 * durationMinute], + [minute, 30, 30 * durationMinute], + [ hour, 1, durationHour ], + [ hour, 3, 3 * durationHour ], + [ hour, 6, 6 * durationHour ], + [ hour, 12, 12 * durationHour ], + [ day, 1, durationDay ], + [ day, 2, 2 * durationDay ], + [ week, 1, durationWeek ], + [ month, 1, durationMonth ], + [ month, 3, 3 * durationMonth ], + [ year, 1, durationYear ] + ]; + + function ticks(start, stop, count) { + const reverse = stop < start; + if (reverse) [start, stop] = [stop, start]; + const interval = count && typeof count.range === "function" ? count : tickInterval(start, stop, count); + const ticks = interval ? interval.range(start, +stop + 1) : []; // inclusive stop + return reverse ? ticks.reverse() : ticks; + } + + function tickInterval(start, stop, count) { + const target = Math.abs(stop - start) / count; + const i = bisector(([,, step]) => step).right(tickIntervals, target); + if (i === tickIntervals.length) return year.every(tickStep(start / durationYear, stop / durationYear, count)); + if (i === 0) return millisecond.every(Math.max(tickStep(start, stop, count), 1)); + const [t, step] = tickIntervals[target / tickIntervals[i - 1][2] < tickIntervals[i][2] / target ? i - 1 : i]; + return t.every(step); + } + + return [ticks, tickInterval]; +} + +const [utcTicks, utcTickInterval] = ticker(utcYear, utcMonth, utcSunday, unixDay, utcHour, utcMinute); +const [timeTicks, timeTickInterval] = ticker(timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute); + +function localDate(d) { + if (0 <= d.y && d.y < 100) { + var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L); + date.setFullYear(d.y); + return date; + } + return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L); +} + +function utcDate(d) { + if (0 <= d.y && d.y < 100) { + var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L)); + date.setUTCFullYear(d.y); + return date; + } + return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L)); +} + +function newDate(y, m, d) { + return {y: y, m: m, d: d, H: 0, M: 0, S: 0, L: 0}; +} + +function formatLocale(locale) { + var locale_dateTime = locale.dateTime, + locale_date = locale.date, + locale_time = locale.time, + locale_periods = locale.periods, + locale_weekdays = locale.days, + locale_shortWeekdays = locale.shortDays, + locale_months = locale.months, + locale_shortMonths = locale.shortMonths; + + var periodRe = formatRe(locale_periods), + periodLookup = formatLookup(locale_periods), + weekdayRe = formatRe(locale_weekdays), + weekdayLookup = formatLookup(locale_weekdays), + shortWeekdayRe = formatRe(locale_shortWeekdays), + shortWeekdayLookup = formatLookup(locale_shortWeekdays), + monthRe = formatRe(locale_months), + monthLookup = formatLookup(locale_months), + shortMonthRe = formatRe(locale_shortMonths), + shortMonthLookup = formatLookup(locale_shortMonths); + + var formats = { + "a": formatShortWeekday, + "A": formatWeekday, + "b": formatShortMonth, + "B": formatMonth, + "c": null, + "d": formatDayOfMonth, + "e": formatDayOfMonth, + "f": formatMicroseconds, + "g": formatYearISO, + "G": formatFullYearISO, + "H": formatHour24, + "I": formatHour12, + "j": formatDayOfYear, + "L": formatMilliseconds, + "m": formatMonthNumber, + "M": formatMinutes, + "p": formatPeriod, + "q": formatQuarter, + "Q": formatUnixTimestamp, + "s": formatUnixTimestampSeconds, + "S": formatSeconds, + "u": formatWeekdayNumberMonday, + "U": formatWeekNumberSunday, + "V": formatWeekNumberISO, + "w": formatWeekdayNumberSunday, + "W": formatWeekNumberMonday, + "x": null, + "X": null, + "y": formatYear, + "Y": formatFullYear, + "Z": formatZone, + "%": formatLiteralPercent + }; + + var utcFormats = { + "a": formatUTCShortWeekday, + "A": formatUTCWeekday, + "b": formatUTCShortMonth, + "B": formatUTCMonth, + "c": null, + "d": formatUTCDayOfMonth, + "e": formatUTCDayOfMonth, + "f": formatUTCMicroseconds, + "g": formatUTCYearISO, + "G": formatUTCFullYearISO, + "H": formatUTCHour24, + "I": formatUTCHour12, + "j": formatUTCDayOfYear, + "L": formatUTCMilliseconds, + "m": formatUTCMonthNumber, + "M": formatUTCMinutes, + "p": formatUTCPeriod, + "q": formatUTCQuarter, + "Q": formatUnixTimestamp, + "s": formatUnixTimestampSeconds, + "S": formatUTCSeconds, + "u": formatUTCWeekdayNumberMonday, + "U": formatUTCWeekNumberSunday, + "V": formatUTCWeekNumberISO, + "w": formatUTCWeekdayNumberSunday, + "W": formatUTCWeekNumberMonday, + "x": null, + "X": null, + "y": formatUTCYear, + "Y": formatUTCFullYear, + "Z": formatUTCZone, + "%": formatLiteralPercent + }; + + var parses = { + "a": parseShortWeekday, + "A": parseWeekday, + "b": parseShortMonth, + "B": parseMonth, + "c": parseLocaleDateTime, + "d": parseDayOfMonth, + "e": parseDayOfMonth, + "f": parseMicroseconds, + "g": parseYear, + "G": parseFullYear, + "H": parseHour24, + "I": parseHour24, + "j": parseDayOfYear, + "L": parseMilliseconds, + "m": parseMonthNumber, + "M": parseMinutes, + "p": parsePeriod, + "q": parseQuarter, + "Q": parseUnixTimestamp, + "s": parseUnixTimestampSeconds, + "S": parseSeconds, + "u": parseWeekdayNumberMonday, + "U": parseWeekNumberSunday, + "V": parseWeekNumberISO, + "w": parseWeekdayNumberSunday, + "W": parseWeekNumberMonday, + "x": parseLocaleDate, + "X": parseLocaleTime, + "y": parseYear, + "Y": parseFullYear, + "Z": parseZone, + "%": parseLiteralPercent + }; + + // These recursive directive definitions must be deferred. + formats.x = newFormat(locale_date, formats); + formats.X = newFormat(locale_time, formats); + formats.c = newFormat(locale_dateTime, formats); + utcFormats.x = newFormat(locale_date, utcFormats); + utcFormats.X = newFormat(locale_time, utcFormats); + utcFormats.c = newFormat(locale_dateTime, utcFormats); + + function newFormat(specifier, formats) { + return function(date) { + var string = [], + i = -1, + j = 0, + n = specifier.length, + c, + pad, + format; + + if (!(date instanceof Date)) date = new Date(+date); + + while (++i < n) { + if (specifier.charCodeAt(i) === 37) { + string.push(specifier.slice(j, i)); + if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i); + else pad = c === "e" ? " " : "0"; + if (format = formats[c]) c = format(date, pad); + string.push(c); + j = i + 1; + } + } + + string.push(specifier.slice(j, i)); + return string.join(""); + }; + } + + function newParse(specifier, Z) { + return function(string) { + var d = newDate(1900, undefined, 1), + i = parseSpecifier(d, specifier, string += "", 0), + week, day; + if (i != string.length) return null; + + // If a UNIX timestamp is specified, return it. + if ("Q" in d) return new Date(d.Q); + if ("s" in d) return new Date(d.s * 1000 + ("L" in d ? d.L : 0)); + + // If this is utcParse, never use the local timezone. + if (Z && !("Z" in d)) d.Z = 0; + + // The am-pm flag is 0 for AM, and 1 for PM. + if ("p" in d) d.H = d.H % 12 + d.p * 12; + + // If the month was not specified, inherit from the quarter. + if (d.m === undefined) d.m = "q" in d ? d.q : 0; + + // Convert day-of-week and week-of-year to day-of-year. + if ("V" in d) { + if (d.V < 1 || d.V > 53) return null; + if (!("w" in d)) d.w = 1; + if ("Z" in d) { + week = utcDate(newDate(d.y, 0, 1)), day = week.getUTCDay(); + week = day > 4 || day === 0 ? utcMonday.ceil(week) : utcMonday(week); + week = utcDay.offset(week, (d.V - 1) * 7); + d.y = week.getUTCFullYear(); + d.m = week.getUTCMonth(); + d.d = week.getUTCDate() + (d.w + 6) % 7; + } else { + week = localDate(newDate(d.y, 0, 1)), day = week.getDay(); + week = day > 4 || day === 0 ? timeMonday.ceil(week) : timeMonday(week); + week = timeDay.offset(week, (d.V - 1) * 7); + d.y = week.getFullYear(); + d.m = week.getMonth(); + d.d = week.getDate() + (d.w + 6) % 7; + } + } else if ("W" in d || "U" in d) { + if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0; + day = "Z" in d ? utcDate(newDate(d.y, 0, 1)).getUTCDay() : localDate(newDate(d.y, 0, 1)).getDay(); + d.m = 0; + d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day + 5) % 7 : d.w + d.U * 7 - (day + 6) % 7; + } + + // If a time zone is specified, all fields are interpreted as UTC and then + // offset according to the specified time zone. + if ("Z" in d) { + d.H += d.Z / 100 | 0; + d.M += d.Z % 100; + return utcDate(d); + } + + // Otherwise, all fields are in local time. + return localDate(d); + }; + } + + function parseSpecifier(d, specifier, string, j) { + var i = 0, + n = specifier.length, + m = string.length, + c, + parse; + + while (i < n) { + if (j >= m) return -1; + c = specifier.charCodeAt(i++); + if (c === 37) { + c = specifier.charAt(i++); + parse = parses[c in pads ? specifier.charAt(i++) : c]; + if (!parse || ((j = parse(d, string, j)) < 0)) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + + return j; + } + + function parsePeriod(d, string, i) { + var n = periodRe.exec(string.slice(i)); + return n ? (d.p = periodLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + + function parseShortWeekday(d, string, i) { + var n = shortWeekdayRe.exec(string.slice(i)); + return n ? (d.w = shortWeekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + + function parseWeekday(d, string, i) { + var n = weekdayRe.exec(string.slice(i)); + return n ? (d.w = weekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + + function parseShortMonth(d, string, i) { + var n = shortMonthRe.exec(string.slice(i)); + return n ? (d.m = shortMonthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + + function parseMonth(d, string, i) { + var n = monthRe.exec(string.slice(i)); + return n ? (d.m = monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + + function parseLocaleDateTime(d, string, i) { + return parseSpecifier(d, locale_dateTime, string, i); + } + + function parseLocaleDate(d, string, i) { + return parseSpecifier(d, locale_date, string, i); + } + + function parseLocaleTime(d, string, i) { + return parseSpecifier(d, locale_time, string, i); + } + + function formatShortWeekday(d) { + return locale_shortWeekdays[d.getDay()]; + } + + function formatWeekday(d) { + return locale_weekdays[d.getDay()]; + } + + function formatShortMonth(d) { + return locale_shortMonths[d.getMonth()]; + } + + function formatMonth(d) { + return locale_months[d.getMonth()]; + } + + function formatPeriod(d) { + return locale_periods[+(d.getHours() >= 12)]; + } + + function formatQuarter(d) { + return 1 + ~~(d.getMonth() / 3); + } + + function formatUTCShortWeekday(d) { + return locale_shortWeekdays[d.getUTCDay()]; + } + + function formatUTCWeekday(d) { + return locale_weekdays[d.getUTCDay()]; + } + + function formatUTCShortMonth(d) { + return locale_shortMonths[d.getUTCMonth()]; + } + + function formatUTCMonth(d) { + return locale_months[d.getUTCMonth()]; + } + + function formatUTCPeriod(d) { + return locale_periods[+(d.getUTCHours() >= 12)]; + } + + function formatUTCQuarter(d) { + return 1 + ~~(d.getUTCMonth() / 3); + } + + return { + format: function(specifier) { + var f = newFormat(specifier += "", formats); + f.toString = function() { return specifier; }; + return f; + }, + parse: function(specifier) { + var p = newParse(specifier += "", false); + p.toString = function() { return specifier; }; + return p; + }, + utcFormat: function(specifier) { + var f = newFormat(specifier += "", utcFormats); + f.toString = function() { return specifier; }; + return f; + }, + utcParse: function(specifier) { + var p = newParse(specifier += "", true); + p.toString = function() { return specifier; }; + return p; + } + }; +} + +var pads = {"-": "", "_": " ", "0": "0"}, + numberRe = /^\s*\d+/, // note: ignores next directive + percentRe = /^%/, + requoteRe = /[\\^$*+?|[\]().{}]/g; + +function pad(value, fill, width) { + var sign = value < 0 ? "-" : "", + string = (sign ? -value : value) + "", + length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); +} + +function requote(s) { + return s.replace(requoteRe, "\\$&"); +} + +function formatRe(names) { + return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i"); +} + +function formatLookup(names) { + return new Map(names.map((name, i) => [name.toLowerCase(), i])); +} + +function parseWeekdayNumberSunday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.w = +n[0], i + n[0].length) : -1; +} + +function parseWeekdayNumberMonday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.u = +n[0], i + n[0].length) : -1; +} + +function parseWeekNumberSunday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.U = +n[0], i + n[0].length) : -1; +} + +function parseWeekNumberISO(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.V = +n[0], i + n[0].length) : -1; +} + +function parseWeekNumberMonday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.W = +n[0], i + n[0].length) : -1; +} + +function parseFullYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 4)); + return n ? (d.y = +n[0], i + n[0].length) : -1; +} + +function parseYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1; +} + +function parseZone(d, string, i) { + var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6)); + return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1; +} + +function parseQuarter(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.q = n[0] * 3 - 3, i + n[0].length) : -1; +} + +function parseMonthNumber(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.m = n[0] - 1, i + n[0].length) : -1; +} + +function parseDayOfMonth(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.d = +n[0], i + n[0].length) : -1; +} + +function parseDayOfYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 3)); + return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1; +} + +function parseHour24(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.H = +n[0], i + n[0].length) : -1; +} + +function parseMinutes(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.M = +n[0], i + n[0].length) : -1; +} + +function parseSeconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.S = +n[0], i + n[0].length) : -1; +} + +function parseMilliseconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 3)); + return n ? (d.L = +n[0], i + n[0].length) : -1; +} + +function parseMicroseconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 6)); + return n ? (d.L = Math.floor(n[0] / 1000), i + n[0].length) : -1; +} + +function parseLiteralPercent(d, string, i) { + var n = percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; +} + +function parseUnixTimestamp(d, string, i) { + var n = numberRe.exec(string.slice(i)); + return n ? (d.Q = +n[0], i + n[0].length) : -1; +} + +function parseUnixTimestampSeconds(d, string, i) { + var n = numberRe.exec(string.slice(i)); + return n ? (d.s = +n[0], i + n[0].length) : -1; +} + +function formatDayOfMonth(d, p) { + return pad(d.getDate(), p, 2); +} + +function formatHour24(d, p) { + return pad(d.getHours(), p, 2); +} + +function formatHour12(d, p) { + return pad(d.getHours() % 12 || 12, p, 2); +} + +function formatDayOfYear(d, p) { + return pad(1 + timeDay.count(timeYear(d), d), p, 3); +} + +function formatMilliseconds(d, p) { + return pad(d.getMilliseconds(), p, 3); +} + +function formatMicroseconds(d, p) { + return formatMilliseconds(d, p) + "000"; +} + +function formatMonthNumber(d, p) { + return pad(d.getMonth() + 1, p, 2); +} + +function formatMinutes(d, p) { + return pad(d.getMinutes(), p, 2); +} + +function formatSeconds(d, p) { + return pad(d.getSeconds(), p, 2); +} + +function formatWeekdayNumberMonday(d) { + var day = d.getDay(); + return day === 0 ? 7 : day; +} + +function formatWeekNumberSunday(d, p) { + return pad(timeSunday.count(timeYear(d) - 1, d), p, 2); +} + +function dISO(d) { + var day = d.getDay(); + return (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d); +} + +function formatWeekNumberISO(d, p) { + d = dISO(d); + return pad(timeThursday.count(timeYear(d), d) + (timeYear(d).getDay() === 4), p, 2); +} + +function formatWeekdayNumberSunday(d) { + return d.getDay(); +} + +function formatWeekNumberMonday(d, p) { + return pad(timeMonday.count(timeYear(d) - 1, d), p, 2); +} + +function formatYear(d, p) { + return pad(d.getFullYear() % 100, p, 2); +} + +function formatYearISO(d, p) { + d = dISO(d); + return pad(d.getFullYear() % 100, p, 2); +} + +function formatFullYear(d, p) { + return pad(d.getFullYear() % 10000, p, 4); +} + +function formatFullYearISO(d, p) { + var day = d.getDay(); + d = (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d); + return pad(d.getFullYear() % 10000, p, 4); +} + +function formatZone(d) { + var z = d.getTimezoneOffset(); + return (z > 0 ? "-" : (z *= -1, "+")) + + pad(z / 60 | 0, "0", 2) + + pad(z % 60, "0", 2); +} + +function formatUTCDayOfMonth(d, p) { + return pad(d.getUTCDate(), p, 2); +} + +function formatUTCHour24(d, p) { + return pad(d.getUTCHours(), p, 2); +} + +function formatUTCHour12(d, p) { + return pad(d.getUTCHours() % 12 || 12, p, 2); +} + +function formatUTCDayOfYear(d, p) { + return pad(1 + utcDay.count(utcYear(d), d), p, 3); +} + +function formatUTCMilliseconds(d, p) { + return pad(d.getUTCMilliseconds(), p, 3); +} + +function formatUTCMicroseconds(d, p) { + return formatUTCMilliseconds(d, p) + "000"; +} + +function formatUTCMonthNumber(d, p) { + return pad(d.getUTCMonth() + 1, p, 2); +} + +function formatUTCMinutes(d, p) { + return pad(d.getUTCMinutes(), p, 2); +} + +function formatUTCSeconds(d, p) { + return pad(d.getUTCSeconds(), p, 2); +} + +function formatUTCWeekdayNumberMonday(d) { + var dow = d.getUTCDay(); + return dow === 0 ? 7 : dow; +} + +function formatUTCWeekNumberSunday(d, p) { + return pad(utcSunday.count(utcYear(d) - 1, d), p, 2); +} + +function UTCdISO(d) { + var day = d.getUTCDay(); + return (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d); +} + +function formatUTCWeekNumberISO(d, p) { + d = UTCdISO(d); + return pad(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2); +} + +function formatUTCWeekdayNumberSunday(d) { + return d.getUTCDay(); +} + +function formatUTCWeekNumberMonday(d, p) { + return pad(utcMonday.count(utcYear(d) - 1, d), p, 2); +} + +function formatUTCYear(d, p) { + return pad(d.getUTCFullYear() % 100, p, 2); +} + +function formatUTCYearISO(d, p) { + d = UTCdISO(d); + return pad(d.getUTCFullYear() % 100, p, 2); +} + +function formatUTCFullYear(d, p) { + return pad(d.getUTCFullYear() % 10000, p, 4); +} + +function formatUTCFullYearISO(d, p) { + var day = d.getUTCDay(); + d = (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d); + return pad(d.getUTCFullYear() % 10000, p, 4); +} + +function formatUTCZone() { + return "+0000"; +} + +function formatLiteralPercent() { + return "%"; +} + +function formatUnixTimestamp(d) { + return +d; +} + +function formatUnixTimestampSeconds(d) { + return Math.floor(+d / 1000); +} + +var locale; +exports.timeFormat = void 0; +exports.timeParse = void 0; +exports.utcFormat = void 0; +exports.utcParse = void 0; + +defaultLocale({ + dateTime: "%x, %X", + date: "%-m/%-d/%Y", + time: "%-I:%M:%S %p", + periods: ["AM", "PM"], + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] +}); + +function defaultLocale(definition) { + locale = formatLocale(definition); + exports.timeFormat = locale.format; + exports.timeParse = locale.parse; + exports.utcFormat = locale.utcFormat; + exports.utcParse = locale.utcParse; + return locale; +} + +var isoSpecifier = "%Y-%m-%dT%H:%M:%S.%LZ"; + +function formatIsoNative(date) { + return date.toISOString(); +} + +var formatIso = Date.prototype.toISOString + ? formatIsoNative + : exports.utcFormat(isoSpecifier); + +var formatIso$1 = formatIso; + +function parseIsoNative(string) { + var date = new Date(string); + return isNaN(date) ? null : date; +} + +var parseIso = +new Date("2000-01-01T00:00:00.000Z") + ? parseIsoNative + : exports.utcParse(isoSpecifier); + +var parseIso$1 = parseIso; + +function date(t) { + return new Date(t); +} + +function number(t) { + return t instanceof Date ? +t : +new Date(+t); +} + +function calendar(ticks, tickInterval, year, month, week, day, hour, minute, second, format) { + var scale = continuous(), + invert = scale.invert, + domain = scale.domain; + + var formatMillisecond = format(".%L"), + formatSecond = format(":%S"), + formatMinute = format("%I:%M"), + formatHour = format("%I %p"), + formatDay = format("%a %d"), + formatWeek = format("%b %d"), + formatMonth = format("%B"), + formatYear = format("%Y"); + + function tickFormat(date) { + return (second(date) < date ? formatMillisecond + : minute(date) < date ? formatSecond + : hour(date) < date ? formatMinute + : day(date) < date ? formatHour + : month(date) < date ? (week(date) < date ? formatDay : formatWeek) + : year(date) < date ? formatMonth + : formatYear)(date); + } + + scale.invert = function(y) { + return new Date(invert(y)); + }; + + scale.domain = function(_) { + return arguments.length ? domain(Array.from(_, number)) : domain().map(date); + }; + + scale.ticks = function(interval) { + var d = domain(); + return ticks(d[0], d[d.length - 1], interval == null ? 10 : interval); + }; + + scale.tickFormat = function(count, specifier) { + return specifier == null ? tickFormat : format(specifier); + }; + + scale.nice = function(interval) { + var d = domain(); + if (!interval || typeof interval.range !== "function") interval = tickInterval(d[0], d[d.length - 1], interval == null ? 10 : interval); + return interval ? domain(nice(d, interval)) : scale; + }; + + scale.copy = function() { + return copy$1(scale, calendar(ticks, tickInterval, year, month, week, day, hour, minute, second, format)); + }; + + return scale; +} + +function time() { + return initRange.apply(calendar(timeTicks, timeTickInterval, timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute, second, exports.timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]), arguments); +} + +function utcTime() { + return initRange.apply(calendar(utcTicks, utcTickInterval, utcYear, utcMonth, utcSunday, utcDay, utcHour, utcMinute, second, exports.utcFormat).domain([Date.UTC(2000, 0, 1), Date.UTC(2000, 0, 2)]), arguments); +} + +function transformer$1() { + var x0 = 0, + x1 = 1, + t0, + t1, + k10, + transform, + interpolator = identity$3, + clamp = false, + unknown; + + function scale(x) { + return x == null || isNaN(x = +x) ? unknown : interpolator(k10 === 0 ? 0.5 : (x = (transform(x) - t0) * k10, clamp ? Math.max(0, Math.min(1, x)) : x)); + } + + scale.domain = function(_) { + return arguments.length ? ([x0, x1] = _, t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), scale) : [x0, x1]; + }; + + scale.clamp = function(_) { + return arguments.length ? (clamp = !!_, scale) : clamp; + }; + + scale.interpolator = function(_) { + return arguments.length ? (interpolator = _, scale) : interpolator; + }; + + function range(interpolate) { + return function(_) { + var r0, r1; + return arguments.length ? ([r0, r1] = _, interpolator = interpolate(r0, r1), scale) : [interpolator(0), interpolator(1)]; + }; + } + + scale.range = range(interpolate$2); + + scale.rangeRound = range(interpolateRound); + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + return function(t) { + transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0); + return scale; + }; +} + +function copy(source, target) { + return target + .domain(source.domain()) + .interpolator(source.interpolator()) + .clamp(source.clamp()) + .unknown(source.unknown()); +} + +function sequential() { + var scale = linearish(transformer$1()(identity$3)); + + scale.copy = function() { + return copy(scale, sequential()); + }; + + return initInterpolator.apply(scale, arguments); +} + +function sequentialLog() { + var scale = loggish(transformer$1()).domain([1, 10]); + + scale.copy = function() { + return copy(scale, sequentialLog()).base(scale.base()); + }; + + return initInterpolator.apply(scale, arguments); +} + +function sequentialSymlog() { + var scale = symlogish(transformer$1()); + + scale.copy = function() { + return copy(scale, sequentialSymlog()).constant(scale.constant()); + }; + + return initInterpolator.apply(scale, arguments); +} + +function sequentialPow() { + var scale = powish(transformer$1()); + + scale.copy = function() { + return copy(scale, sequentialPow()).exponent(scale.exponent()); + }; + + return initInterpolator.apply(scale, arguments); +} + +function sequentialSqrt() { + return sequentialPow.apply(null, arguments).exponent(0.5); +} + +function sequentialQuantile() { + var domain = [], + interpolator = identity$3; + + function scale(x) { + if (x != null && !isNaN(x = +x)) return interpolator((bisect(domain, x, 1) - 1) / (domain.length - 1)); + } + + scale.domain = function(_) { + if (!arguments.length) return domain.slice(); + domain = []; + for (let d of _) if (d != null && !isNaN(d = +d)) domain.push(d); + domain.sort(ascending$3); + return scale; + }; + + scale.interpolator = function(_) { + return arguments.length ? (interpolator = _, scale) : interpolator; + }; + + scale.range = function() { + return domain.map((d, i) => interpolator(i / (domain.length - 1))); + }; + + scale.quantiles = function(n) { + return Array.from({length: n + 1}, (_, i) => quantile$1(domain, i / n)); + }; + + scale.copy = function() { + return sequentialQuantile(interpolator).domain(domain); + }; + + return initInterpolator.apply(scale, arguments); +} + +function transformer() { + var x0 = 0, + x1 = 0.5, + x2 = 1, + s = 1, + t0, + t1, + t2, + k10, + k21, + interpolator = identity$3, + transform, + clamp = false, + unknown; + + function scale(x) { + return isNaN(x = +x) ? unknown : (x = 0.5 + ((x = +transform(x)) - t1) * (s * x < s * t1 ? k10 : k21), interpolator(clamp ? Math.max(0, Math.min(1, x)) : x)); + } + + scale.domain = function(_) { + return arguments.length ? ([x0, x1, x2] = _, t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), t2 = transform(x2 = +x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1, scale) : [x0, x1, x2]; + }; + + scale.clamp = function(_) { + return arguments.length ? (clamp = !!_, scale) : clamp; + }; + + scale.interpolator = function(_) { + return arguments.length ? (interpolator = _, scale) : interpolator; + }; + + function range(interpolate) { + return function(_) { + var r0, r1, r2; + return arguments.length ? ([r0, r1, r2] = _, interpolator = piecewise(interpolate, [r0, r1, r2]), scale) : [interpolator(0), interpolator(0.5), interpolator(1)]; + }; + } + + scale.range = range(interpolate$2); + + scale.rangeRound = range(interpolateRound); + + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + return function(t) { + transform = t, t0 = t(x0), t1 = t(x1), t2 = t(x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1; + return scale; + }; +} + +function diverging$1() { + var scale = linearish(transformer()(identity$3)); + + scale.copy = function() { + return copy(scale, diverging$1()); + }; + + return initInterpolator.apply(scale, arguments); +} + +function divergingLog() { + var scale = loggish(transformer()).domain([0.1, 1, 10]); + + scale.copy = function() { + return copy(scale, divergingLog()).base(scale.base()); + }; + + return initInterpolator.apply(scale, arguments); +} + +function divergingSymlog() { + var scale = symlogish(transformer()); + + scale.copy = function() { + return copy(scale, divergingSymlog()).constant(scale.constant()); + }; + + return initInterpolator.apply(scale, arguments); +} + +function divergingPow() { + var scale = powish(transformer()); + + scale.copy = function() { + return copy(scale, divergingPow()).exponent(scale.exponent()); + }; + + return initInterpolator.apply(scale, arguments); +} + +function divergingSqrt() { + return divergingPow.apply(null, arguments).exponent(0.5); +} + +function colors(specifier) { + var n = specifier.length / 6 | 0, colors = new Array(n), i = 0; + while (i < n) colors[i] = "#" + specifier.slice(i * 6, ++i * 6); + return colors; +} + +var category10 = colors("1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf"); + +var Accent = colors("7fc97fbeaed4fdc086ffff99386cb0f0027fbf5b17666666"); + +var Dark2 = colors("1b9e77d95f027570b3e7298a66a61ee6ab02a6761d666666"); + +var Paired = colors("a6cee31f78b4b2df8a33a02cfb9a99e31a1cfdbf6fff7f00cab2d66a3d9affff99b15928"); + +var Pastel1 = colors("fbb4aeb3cde3ccebc5decbe4fed9a6ffffcce5d8bdfddaecf2f2f2"); + +var Pastel2 = colors("b3e2cdfdcdaccbd5e8f4cae4e6f5c9fff2aef1e2cccccccc"); + +var Set1 = colors("e41a1c377eb84daf4a984ea3ff7f00ffff33a65628f781bf999999"); + +var Set2 = colors("66c2a5fc8d628da0cbe78ac3a6d854ffd92fe5c494b3b3b3"); + +var Set3 = colors("8dd3c7ffffb3bebadafb807280b1d3fdb462b3de69fccde5d9d9d9bc80bdccebc5ffed6f"); + +var Tableau10 = colors("4e79a7f28e2ce1575976b7b259a14fedc949af7aa1ff9da79c755fbab0ab"); + +var ramp$1 = scheme => rgbBasis(scheme[scheme.length - 1]); + +var scheme$q = new Array(3).concat( + "d8b365f5f5f55ab4ac", + "a6611adfc27d80cdc1018571", + "a6611adfc27df5f5f580cdc1018571", + "8c510ad8b365f6e8c3c7eae55ab4ac01665e", + "8c510ad8b365f6e8c3f5f5f5c7eae55ab4ac01665e", + "8c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e", + "8c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e", + "5430058c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e003c30", + "5430058c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e003c30" +).map(colors); + +var BrBG = ramp$1(scheme$q); + +var scheme$p = new Array(3).concat( + "af8dc3f7f7f77fbf7b", + "7b3294c2a5cfa6dba0008837", + "7b3294c2a5cff7f7f7a6dba0008837", + "762a83af8dc3e7d4e8d9f0d37fbf7b1b7837", + "762a83af8dc3e7d4e8f7f7f7d9f0d37fbf7b1b7837", + "762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b7837", + "762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b7837", + "40004b762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b783700441b", + "40004b762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b783700441b" +).map(colors); + +var PRGn = ramp$1(scheme$p); + +var scheme$o = new Array(3).concat( + "e9a3c9f7f7f7a1d76a", + "d01c8bf1b6dab8e1864dac26", + "d01c8bf1b6daf7f7f7b8e1864dac26", + "c51b7de9a3c9fde0efe6f5d0a1d76a4d9221", + "c51b7de9a3c9fde0eff7f7f7e6f5d0a1d76a4d9221", + "c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221", + "c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221", + "8e0152c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221276419", + "8e0152c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221276419" +).map(colors); + +var PiYG = ramp$1(scheme$o); + +var scheme$n = new Array(3).concat( + "998ec3f7f7f7f1a340", + "5e3c99b2abd2fdb863e66101", + "5e3c99b2abd2f7f7f7fdb863e66101", + "542788998ec3d8daebfee0b6f1a340b35806", + "542788998ec3d8daebf7f7f7fee0b6f1a340b35806", + "5427888073acb2abd2d8daebfee0b6fdb863e08214b35806", + "5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b35806", + "2d004b5427888073acb2abd2d8daebfee0b6fdb863e08214b358067f3b08", + "2d004b5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b358067f3b08" +).map(colors); + +var PuOr = ramp$1(scheme$n); + +var scheme$m = new Array(3).concat( + "ef8a62f7f7f767a9cf", + "ca0020f4a58292c5de0571b0", + "ca0020f4a582f7f7f792c5de0571b0", + "b2182bef8a62fddbc7d1e5f067a9cf2166ac", + "b2182bef8a62fddbc7f7f7f7d1e5f067a9cf2166ac", + "b2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac", + "b2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac", + "67001fb2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac053061", + "67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061" +).map(colors); + +var RdBu = ramp$1(scheme$m); + +var scheme$l = new Array(3).concat( + "ef8a62ffffff999999", + "ca0020f4a582bababa404040", + "ca0020f4a582ffffffbababa404040", + "b2182bef8a62fddbc7e0e0e09999994d4d4d", + "b2182bef8a62fddbc7ffffffe0e0e09999994d4d4d", + "b2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d", + "b2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d", + "67001fb2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d1a1a1a", + "67001fb2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d1a1a1a" +).map(colors); + +var RdGy = ramp$1(scheme$l); + +var scheme$k = new Array(3).concat( + "fc8d59ffffbf91bfdb", + "d7191cfdae61abd9e92c7bb6", + "d7191cfdae61ffffbfabd9e92c7bb6", + "d73027fc8d59fee090e0f3f891bfdb4575b4", + "d73027fc8d59fee090ffffbfe0f3f891bfdb4575b4", + "d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4", + "d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4", + "a50026d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4313695", + "a50026d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4313695" +).map(colors); + +var RdYlBu = ramp$1(scheme$k); + +var scheme$j = new Array(3).concat( + "fc8d59ffffbf91cf60", + "d7191cfdae61a6d96a1a9641", + "d7191cfdae61ffffbfa6d96a1a9641", + "d73027fc8d59fee08bd9ef8b91cf601a9850", + "d73027fc8d59fee08bffffbfd9ef8b91cf601a9850", + "d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850", + "d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850", + "a50026d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850006837", + "a50026d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850006837" +).map(colors); + +var RdYlGn = ramp$1(scheme$j); + +var scheme$i = new Array(3).concat( + "fc8d59ffffbf99d594", + "d7191cfdae61abdda42b83ba", + "d7191cfdae61ffffbfabdda42b83ba", + "d53e4ffc8d59fee08be6f59899d5943288bd", + "d53e4ffc8d59fee08bffffbfe6f59899d5943288bd", + "d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd", + "d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd", + "9e0142d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd5e4fa2", + "9e0142d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd5e4fa2" +).map(colors); + +var Spectral = ramp$1(scheme$i); + +var scheme$h = new Array(3).concat( + "e5f5f999d8c92ca25f", + "edf8fbb2e2e266c2a4238b45", + "edf8fbb2e2e266c2a42ca25f006d2c", + "edf8fbccece699d8c966c2a42ca25f006d2c", + "edf8fbccece699d8c966c2a441ae76238b45005824", + "f7fcfde5f5f9ccece699d8c966c2a441ae76238b45005824", + "f7fcfde5f5f9ccece699d8c966c2a441ae76238b45006d2c00441b" +).map(colors); + +var BuGn = ramp$1(scheme$h); + +var scheme$g = new Array(3).concat( + "e0ecf49ebcda8856a7", + "edf8fbb3cde38c96c688419d", + "edf8fbb3cde38c96c68856a7810f7c", + "edf8fbbfd3e69ebcda8c96c68856a7810f7c", + "edf8fbbfd3e69ebcda8c96c68c6bb188419d6e016b", + "f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d6e016b", + "f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d810f7c4d004b" +).map(colors); + +var BuPu = ramp$1(scheme$g); + +var scheme$f = new Array(3).concat( + "e0f3dba8ddb543a2ca", + "f0f9e8bae4bc7bccc42b8cbe", + "f0f9e8bae4bc7bccc443a2ca0868ac", + "f0f9e8ccebc5a8ddb57bccc443a2ca0868ac", + "f0f9e8ccebc5a8ddb57bccc44eb3d32b8cbe08589e", + "f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe08589e", + "f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe0868ac084081" +).map(colors); + +var GnBu = ramp$1(scheme$f); + +var scheme$e = new Array(3).concat( + "fee8c8fdbb84e34a33", + "fef0d9fdcc8afc8d59d7301f", + "fef0d9fdcc8afc8d59e34a33b30000", + "fef0d9fdd49efdbb84fc8d59e34a33b30000", + "fef0d9fdd49efdbb84fc8d59ef6548d7301f990000", + "fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301f990000", + "fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301fb300007f0000" +).map(colors); + +var OrRd = ramp$1(scheme$e); + +var scheme$d = new Array(3).concat( + "ece2f0a6bddb1c9099", + "f6eff7bdc9e167a9cf02818a", + "f6eff7bdc9e167a9cf1c9099016c59", + "f6eff7d0d1e6a6bddb67a9cf1c9099016c59", + "f6eff7d0d1e6a6bddb67a9cf3690c002818a016450", + "fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016450", + "fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016c59014636" +).map(colors); + +var PuBuGn = ramp$1(scheme$d); + +var scheme$c = new Array(3).concat( + "ece7f2a6bddb2b8cbe", + "f1eef6bdc9e174a9cf0570b0", + "f1eef6bdc9e174a9cf2b8cbe045a8d", + "f1eef6d0d1e6a6bddb74a9cf2b8cbe045a8d", + "f1eef6d0d1e6a6bddb74a9cf3690c00570b0034e7b", + "fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0034e7b", + "fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0045a8d023858" +).map(colors); + +var PuBu = ramp$1(scheme$c); + +var scheme$b = new Array(3).concat( + "e7e1efc994c7dd1c77", + "f1eef6d7b5d8df65b0ce1256", + "f1eef6d7b5d8df65b0dd1c77980043", + "f1eef6d4b9dac994c7df65b0dd1c77980043", + "f1eef6d4b9dac994c7df65b0e7298ace125691003f", + "f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125691003f", + "f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125698004367001f" +).map(colors); + +var PuRd = ramp$1(scheme$b); + +var scheme$a = new Array(3).concat( + "fde0ddfa9fb5c51b8a", + "feebe2fbb4b9f768a1ae017e", + "feebe2fbb4b9f768a1c51b8a7a0177", + "feebe2fcc5c0fa9fb5f768a1c51b8a7a0177", + "feebe2fcc5c0fa9fb5f768a1dd3497ae017e7a0177", + "fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a0177", + "fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a017749006a" +).map(colors); + +var RdPu = ramp$1(scheme$a); + +var scheme$9 = new Array(3).concat( + "edf8b17fcdbb2c7fb8", + "ffffcca1dab441b6c4225ea8", + "ffffcca1dab441b6c42c7fb8253494", + "ffffccc7e9b47fcdbb41b6c42c7fb8253494", + "ffffccc7e9b47fcdbb41b6c41d91c0225ea80c2c84", + "ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea80c2c84", + "ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea8253494081d58" +).map(colors); + +var YlGnBu = ramp$1(scheme$9); + +var scheme$8 = new Array(3).concat( + "f7fcb9addd8e31a354", + "ffffccc2e69978c679238443", + "ffffccc2e69978c67931a354006837", + "ffffccd9f0a3addd8e78c67931a354006837", + "ffffccd9f0a3addd8e78c67941ab5d238443005a32", + "ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443005a32", + "ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443006837004529" +).map(colors); + +var YlGn = ramp$1(scheme$8); + +var scheme$7 = new Array(3).concat( + "fff7bcfec44fd95f0e", + "ffffd4fed98efe9929cc4c02", + "ffffd4fed98efe9929d95f0e993404", + "ffffd4fee391fec44ffe9929d95f0e993404", + "ffffd4fee391fec44ffe9929ec7014cc4c028c2d04", + "ffffe5fff7bcfee391fec44ffe9929ec7014cc4c028c2d04", + "ffffe5fff7bcfee391fec44ffe9929ec7014cc4c02993404662506" +).map(colors); + +var YlOrBr = ramp$1(scheme$7); + +var scheme$6 = new Array(3).concat( + "ffeda0feb24cf03b20", + "ffffb2fecc5cfd8d3ce31a1c", + "ffffb2fecc5cfd8d3cf03b20bd0026", + "ffffb2fed976feb24cfd8d3cf03b20bd0026", + "ffffb2fed976feb24cfd8d3cfc4e2ae31a1cb10026", + "ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cb10026", + "ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cbd0026800026" +).map(colors); + +var YlOrRd = ramp$1(scheme$6); + +var scheme$5 = new Array(3).concat( + "deebf79ecae13182bd", + "eff3ffbdd7e76baed62171b5", + "eff3ffbdd7e76baed63182bd08519c", + "eff3ffc6dbef9ecae16baed63182bd08519c", + "eff3ffc6dbef9ecae16baed64292c62171b5084594", + "f7fbffdeebf7c6dbef9ecae16baed64292c62171b5084594", + "f7fbffdeebf7c6dbef9ecae16baed64292c62171b508519c08306b" +).map(colors); + +var Blues = ramp$1(scheme$5); + +var scheme$4 = new Array(3).concat( + "e5f5e0a1d99b31a354", + "edf8e9bae4b374c476238b45", + "edf8e9bae4b374c47631a354006d2c", + "edf8e9c7e9c0a1d99b74c47631a354006d2c", + "edf8e9c7e9c0a1d99b74c47641ab5d238b45005a32", + "f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45005a32", + "f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45006d2c00441b" +).map(colors); + +var Greens = ramp$1(scheme$4); + +var scheme$3 = new Array(3).concat( + "f0f0f0bdbdbd636363", + "f7f7f7cccccc969696525252", + "f7f7f7cccccc969696636363252525", + "f7f7f7d9d9d9bdbdbd969696636363252525", + "f7f7f7d9d9d9bdbdbd969696737373525252252525", + "fffffff0f0f0d9d9d9bdbdbd969696737373525252252525", + "fffffff0f0f0d9d9d9bdbdbd969696737373525252252525000000" +).map(colors); + +var Greys = ramp$1(scheme$3); + +var scheme$2 = new Array(3).concat( + "efedf5bcbddc756bb1", + "f2f0f7cbc9e29e9ac86a51a3", + "f2f0f7cbc9e29e9ac8756bb154278f", + "f2f0f7dadaebbcbddc9e9ac8756bb154278f", + "f2f0f7dadaebbcbddc9e9ac8807dba6a51a34a1486", + "fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a34a1486", + "fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a354278f3f007d" +).map(colors); + +var Purples = ramp$1(scheme$2); + +var scheme$1 = new Array(3).concat( + "fee0d2fc9272de2d26", + "fee5d9fcae91fb6a4acb181d", + "fee5d9fcae91fb6a4ade2d26a50f15", + "fee5d9fcbba1fc9272fb6a4ade2d26a50f15", + "fee5d9fcbba1fc9272fb6a4aef3b2ccb181d99000d", + "fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181d99000d", + "fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181da50f1567000d" +).map(colors); + +var Reds = ramp$1(scheme$1); + +var scheme = new Array(3).concat( + "fee6cefdae6be6550d", + "feeddefdbe85fd8d3cd94701", + "feeddefdbe85fd8d3ce6550da63603", + "feeddefdd0a2fdae6bfd8d3ce6550da63603", + "feeddefdd0a2fdae6bfd8d3cf16913d948018c2d04", + "fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d948018c2d04", + "fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d94801a636037f2704" +).map(colors); + +var Oranges = ramp$1(scheme); + +function cividis(t) { + t = Math.max(0, Math.min(1, t)); + return "rgb(" + + Math.max(0, Math.min(255, Math.round(-4.54 - t * (35.34 - t * (2381.73 - t * (6402.7 - t * (7024.72 - t * 2710.57))))))) + ", " + + Math.max(0, Math.min(255, Math.round(32.49 + t * (170.73 + t * (52.82 - t * (131.46 - t * (176.58 - t * 67.37))))))) + ", " + + Math.max(0, Math.min(255, Math.round(81.24 + t * (442.36 - t * (2482.43 - t * (6167.24 - t * (6614.94 - t * 2475.67))))))) + + ")"; +} + +var cubehelix = cubehelixLong(cubehelix$3(300, 0.5, 0.0), cubehelix$3(-240, 0.5, 1.0)); + +var warm = cubehelixLong(cubehelix$3(-100, 0.75, 0.35), cubehelix$3(80, 1.50, 0.8)); + +var cool = cubehelixLong(cubehelix$3(260, 0.75, 0.35), cubehelix$3(80, 1.50, 0.8)); + +var c$2 = cubehelix$3(); + +function rainbow(t) { + if (t < 0 || t > 1) t -= Math.floor(t); + var ts = Math.abs(t - 0.5); + c$2.h = 360 * t - 100; + c$2.s = 1.5 - 1.5 * ts; + c$2.l = 0.8 - 0.9 * ts; + return c$2 + ""; +} + +var c$1 = rgb(), + pi_1_3 = Math.PI / 3, + pi_2_3 = Math.PI * 2 / 3; + +function sinebow(t) { + var x; + t = (0.5 - t) * Math.PI; + c$1.r = 255 * (x = Math.sin(t)) * x; + c$1.g = 255 * (x = Math.sin(t + pi_1_3)) * x; + c$1.b = 255 * (x = Math.sin(t + pi_2_3)) * x; + return c$1 + ""; +} + +function turbo(t) { + t = Math.max(0, Math.min(1, t)); + return "rgb(" + + Math.max(0, Math.min(255, Math.round(34.61 + t * (1172.33 - t * (10793.56 - t * (33300.12 - t * (38394.49 - t * 14825.05))))))) + ", " + + Math.max(0, Math.min(255, Math.round(23.31 + t * (557.33 + t * (1225.33 - t * (3574.96 - t * (1073.77 + t * 707.56))))))) + ", " + + Math.max(0, Math.min(255, Math.round(27.2 + t * (3211.1 - t * (15327.97 - t * (27814 - t * (22569.18 - t * 6838.66))))))) + + ")"; +} + +function ramp(range) { + var n = range.length; + return function(t) { + return range[Math.max(0, Math.min(n - 1, Math.floor(t * n)))]; + }; +} + +var viridis = ramp(colors("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")); + +var magma = ramp(colors("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")); + +var inferno = ramp(colors("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")); + +var plasma = ramp(colors("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")); + +function constant$1(x) { + return function constant() { + return x; + }; +} + +const abs = Math.abs; +const atan2 = Math.atan2; +const cos = Math.cos; +const max = Math.max; +const min = Math.min; +const sin = Math.sin; +const sqrt = Math.sqrt; + +const epsilon = 1e-12; +const pi = Math.PI; +const halfPi = pi / 2; +const tau = 2 * pi; + +function acos(x) { + return x > 1 ? 0 : x < -1 ? pi : Math.acos(x); +} + +function asin(x) { + return x >= 1 ? halfPi : x <= -1 ? -halfPi : Math.asin(x); +} + +function withPath(shape) { + let digits = 3; + + shape.digits = function(_) { + if (!arguments.length) return digits; + if (_ == null) { + digits = null; + } else { + const d = Math.floor(_); + if (!(d >= 0)) throw new RangeError(`invalid digits: ${_}`); + digits = d; + } + return shape; + }; + + return () => new Path$1(digits); +} + +function arcInnerRadius(d) { + return d.innerRadius; +} + +function arcOuterRadius(d) { + return d.outerRadius; +} + +function arcStartAngle(d) { + return d.startAngle; +} + +function arcEndAngle(d) { + return d.endAngle; +} + +function arcPadAngle(d) { + return d && d.padAngle; // Note: optional! +} + +function intersect(x0, y0, x1, y1, x2, y2, x3, y3) { + var x10 = x1 - x0, y10 = y1 - y0, + x32 = x3 - x2, y32 = y3 - y2, + t = y32 * x10 - x32 * y10; + if (t * t < epsilon) return; + t = (x32 * (y0 - y2) - y32 * (x0 - x2)) / t; + return [x0 + t * x10, y0 + t * y10]; +} + +// Compute perpendicular offset line of length rc. +// http://mathworld.wolfram.com/Circle-LineIntersection.html +function cornerTangents(x0, y0, x1, y1, r1, rc, cw) { + var x01 = x0 - x1, + y01 = y0 - y1, + lo = (cw ? rc : -rc) / sqrt(x01 * x01 + y01 * y01), + ox = lo * y01, + oy = -lo * x01, + x11 = x0 + ox, + y11 = y0 + oy, + x10 = x1 + ox, + y10 = y1 + oy, + x00 = (x11 + x10) / 2, + y00 = (y11 + y10) / 2, + dx = x10 - x11, + dy = y10 - y11, + d2 = dx * dx + dy * dy, + r = r1 - rc, + D = x11 * y10 - x10 * y11, + d = (dy < 0 ? -1 : 1) * sqrt(max(0, r * r * d2 - D * D)), + cx0 = (D * dy - dx * d) / d2, + cy0 = (-D * dx - dy * d) / d2, + cx1 = (D * dy + dx * d) / d2, + cy1 = (-D * dx + dy * d) / d2, + dx0 = cx0 - x00, + dy0 = cy0 - y00, + dx1 = cx1 - x00, + dy1 = cy1 - y00; + + // Pick the closer of the two intersection points. + // TODO Is there a faster way to determine which intersection to use? + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; + + return { + cx: cx0, + cy: cy0, + x01: -ox, + y01: -oy, + x11: cx0 * (r1 / r - 1), + y11: cy0 * (r1 / r - 1) + }; +} + +function arc() { + var innerRadius = arcInnerRadius, + outerRadius = arcOuterRadius, + cornerRadius = constant$1(0), + padRadius = null, + startAngle = arcStartAngle, + endAngle = arcEndAngle, + padAngle = arcPadAngle, + context = null, + path = withPath(arc); + + function arc() { + var buffer, + r, + r0 = +innerRadius.apply(this, arguments), + r1 = +outerRadius.apply(this, arguments), + a0 = startAngle.apply(this, arguments) - halfPi, + a1 = endAngle.apply(this, arguments) - halfPi, + da = abs(a1 - a0), + cw = a1 > a0; + + if (!context) context = buffer = path(); + + // Ensure that the outer radius is always larger than the inner radius. + if (r1 < r0) r = r1, r1 = r0, r0 = r; + + // Is it a point? + if (!(r1 > epsilon)) context.moveTo(0, 0); + + // Or is it a circle or annulus? + else if (da > tau - epsilon) { + context.moveTo(r1 * cos(a0), r1 * sin(a0)); + context.arc(0, 0, r1, a0, a1, !cw); + if (r0 > epsilon) { + context.moveTo(r0 * cos(a1), r0 * sin(a1)); + context.arc(0, 0, r0, a1, a0, cw); + } + } + + // Or is it a circular or annular sector? + else { + var a01 = a0, + a11 = a1, + a00 = a0, + a10 = a1, + da0 = da, + da1 = da, + ap = padAngle.apply(this, arguments) / 2, + rp = (ap > epsilon) && (padRadius ? +padRadius.apply(this, arguments) : sqrt(r0 * r0 + r1 * r1)), + rc = min(abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments)), + rc0 = rc, + rc1 = rc, + t0, + t1; + + // Apply padding? Note that since r1 ≥ r0, da1 ≥ da0. + if (rp > epsilon) { + var p0 = asin(rp / r0 * sin(ap)), + p1 = asin(rp / r1 * sin(ap)); + if ((da0 -= p0 * 2) > epsilon) p0 *= (cw ? 1 : -1), a00 += p0, a10 -= p0; + else da0 = 0, a00 = a10 = (a0 + a1) / 2; + if ((da1 -= p1 * 2) > epsilon) p1 *= (cw ? 1 : -1), a01 += p1, a11 -= p1; + else da1 = 0, a01 = a11 = (a0 + a1) / 2; + } + + var x01 = r1 * cos(a01), + y01 = r1 * sin(a01), + x10 = r0 * cos(a10), + y10 = r0 * sin(a10); + + // Apply rounded corners? + if (rc > epsilon) { + var x11 = r1 * cos(a11), + y11 = r1 * sin(a11), + x00 = r0 * cos(a00), + y00 = r0 * sin(a00), + oc; + + // Restrict the corner radius according to the sector angle. If this + // intersection fails, it’s probably because the arc is too small, so + // disable the corner radius entirely. + if (da < pi) { + if (oc = intersect(x01, y01, x00, y00, x11, y11, x10, y10)) { + var ax = x01 - oc[0], + ay = y01 - oc[1], + bx = x11 - oc[0], + by = y11 - oc[1], + kc = 1 / sin(acos((ax * bx + ay * by) / (sqrt(ax * ax + ay * ay) * sqrt(bx * bx + by * by))) / 2), + lc = sqrt(oc[0] * oc[0] + oc[1] * oc[1]); + rc0 = min(rc, (r0 - lc) / (kc - 1)); + rc1 = min(rc, (r1 - lc) / (kc + 1)); + } else { + rc0 = rc1 = 0; + } + } + } + + // Is the sector collapsed to a line? + if (!(da1 > epsilon)) context.moveTo(x01, y01); + + // Does the sector’s outer ring have rounded corners? + else if (rc1 > epsilon) { + t0 = cornerTangents(x00, y00, x01, y01, r1, rc1, cw); + t1 = cornerTangents(x11, y11, x10, y10, r1, rc1, cw); + + context.moveTo(t0.cx + t0.x01, t0.cy + t0.y01); + + // Have the corners merged? + if (rc1 < rc) context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw); + + // Otherwise, draw the two corners and the ring. + else { + context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw); + context.arc(0, 0, r1, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), !cw); + context.arc(t1.cx, t1.cy, rc1, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw); + } + } + + // Or is the outer ring just a circular arc? + else context.moveTo(x01, y01), context.arc(0, 0, r1, a01, a11, !cw); + + // Is there no inner ring, and it’s a circular sector? + // Or perhaps it’s an annular sector collapsed due to padding? + if (!(r0 > epsilon) || !(da0 > epsilon)) context.lineTo(x10, y10); + + // Does the sector’s inner ring (or point) have rounded corners? + else if (rc0 > epsilon) { + t0 = cornerTangents(x10, y10, x11, y11, r0, -rc0, cw); + t1 = cornerTangents(x01, y01, x00, y00, r0, -rc0, cw); + + context.lineTo(t0.cx + t0.x01, t0.cy + t0.y01); + + // Have the corners merged? + if (rc0 < rc) context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw); + + // Otherwise, draw the two corners and the ring. + else { + context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw); + context.arc(0, 0, r0, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), cw); + context.arc(t1.cx, t1.cy, rc0, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw); + } + } + + // Or is the inner ring just a circular arc? + else context.arc(0, 0, r0, a10, a00, cw); + } + + context.closePath(); + + if (buffer) return context = null, buffer + "" || null; + } + + arc.centroid = function() { + var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, + a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - pi / 2; + return [cos(a) * r, sin(a) * r]; + }; + + arc.innerRadius = function(_) { + return arguments.length ? (innerRadius = typeof _ === "function" ? _ : constant$1(+_), arc) : innerRadius; + }; + + arc.outerRadius = function(_) { + return arguments.length ? (outerRadius = typeof _ === "function" ? _ : constant$1(+_), arc) : outerRadius; + }; + + arc.cornerRadius = function(_) { + return arguments.length ? (cornerRadius = typeof _ === "function" ? _ : constant$1(+_), arc) : cornerRadius; + }; + + arc.padRadius = function(_) { + return arguments.length ? (padRadius = _ == null ? null : typeof _ === "function" ? _ : constant$1(+_), arc) : padRadius; + }; + + arc.startAngle = function(_) { + return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant$1(+_), arc) : startAngle; + }; + + arc.endAngle = function(_) { + return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant$1(+_), arc) : endAngle; + }; + + arc.padAngle = function(_) { + return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant$1(+_), arc) : padAngle; + }; + + arc.context = function(_) { + return arguments.length ? ((context = _ == null ? null : _), arc) : context; + }; + + return arc; +} + +var slice = Array.prototype.slice; + +function array(x) { + return typeof x === "object" && "length" in x + ? x // Array, TypedArray, NodeList, array-like + : Array.from(x); // Map, Set, iterable, string, or anything else +} + +function Linear(context) { + this._context = context; +} + +Linear.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._point = 0; + }, + lineEnd: function() { + if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); + this._line = 1 - this._line; + }, + point: function(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; + case 1: this._point = 2; // falls through + default: this._context.lineTo(x, y); break; + } + } +}; + +function curveLinear(context) { + return new Linear(context); +} + +function x(p) { + return p[0]; +} + +function y(p) { + return p[1]; +} + +function line(x$1, y$1) { + var defined = constant$1(true), + context = null, + curve = curveLinear, + output = null, + path = withPath(line); + + x$1 = typeof x$1 === "function" ? x$1 : (x$1 === undefined) ? x : constant$1(x$1); + y$1 = typeof y$1 === "function" ? y$1 : (y$1 === undefined) ? y : constant$1(y$1); + + function line(data) { + var i, + n = (data = array(data)).length, + d, + defined0 = false, + buffer; + + if (context == null) output = curve(buffer = path()); + + for (i = 0; i <= n; ++i) { + if (!(i < n && defined(d = data[i], i, data)) === defined0) { + if (defined0 = !defined0) output.lineStart(); + else output.lineEnd(); + } + if (defined0) output.point(+x$1(d, i, data), +y$1(d, i, data)); + } + + if (buffer) return output = null, buffer + "" || null; + } + + line.x = function(_) { + return arguments.length ? (x$1 = typeof _ === "function" ? _ : constant$1(+_), line) : x$1; + }; + + line.y = function(_) { + return arguments.length ? (y$1 = typeof _ === "function" ? _ : constant$1(+_), line) : y$1; + }; + + line.defined = function(_) { + return arguments.length ? (defined = typeof _ === "function" ? _ : constant$1(!!_), line) : defined; + }; + + line.curve = function(_) { + return arguments.length ? (curve = _, context != null && (output = curve(context)), line) : curve; + }; + + line.context = function(_) { + return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), line) : context; + }; + + return line; +} + +function area(x0, y0, y1) { + var x1 = null, + defined = constant$1(true), + context = null, + curve = curveLinear, + output = null, + path = withPath(area); + + x0 = typeof x0 === "function" ? x0 : (x0 === undefined) ? x : constant$1(+x0); + y0 = typeof y0 === "function" ? y0 : (y0 === undefined) ? constant$1(0) : constant$1(+y0); + y1 = typeof y1 === "function" ? y1 : (y1 === undefined) ? y : constant$1(+y1); + + function area(data) { + var i, + j, + k, + n = (data = array(data)).length, + d, + defined0 = false, + buffer, + x0z = new Array(n), + y0z = new Array(n); + + if (context == null) output = curve(buffer = path()); + + for (i = 0; i <= n; ++i) { + if (!(i < n && defined(d = data[i], i, data)) === defined0) { + if (defined0 = !defined0) { + j = i; + output.areaStart(); + output.lineStart(); + } else { + output.lineEnd(); + output.lineStart(); + for (k = i - 1; k >= j; --k) { + output.point(x0z[k], y0z[k]); + } + output.lineEnd(); + output.areaEnd(); + } + } + if (defined0) { + x0z[i] = +x0(d, i, data), y0z[i] = +y0(d, i, data); + output.point(x1 ? +x1(d, i, data) : x0z[i], y1 ? +y1(d, i, data) : y0z[i]); + } + } + + if (buffer) return output = null, buffer + "" || null; + } + + function arealine() { + return line().defined(defined).curve(curve).context(context); + } + + area.x = function(_) { + return arguments.length ? (x0 = typeof _ === "function" ? _ : constant$1(+_), x1 = null, area) : x0; + }; + + area.x0 = function(_) { + return arguments.length ? (x0 = typeof _ === "function" ? _ : constant$1(+_), area) : x0; + }; + + area.x1 = function(_) { + return arguments.length ? (x1 = _ == null ? null : typeof _ === "function" ? _ : constant$1(+_), area) : x1; + }; + + area.y = function(_) { + return arguments.length ? (y0 = typeof _ === "function" ? _ : constant$1(+_), y1 = null, area) : y0; + }; + + area.y0 = function(_) { + return arguments.length ? (y0 = typeof _ === "function" ? _ : constant$1(+_), area) : y0; + }; + + area.y1 = function(_) { + return arguments.length ? (y1 = _ == null ? null : typeof _ === "function" ? _ : constant$1(+_), area) : y1; + }; + + area.lineX0 = + area.lineY0 = function() { + return arealine().x(x0).y(y0); + }; + + area.lineY1 = function() { + return arealine().x(x0).y(y1); + }; + + area.lineX1 = function() { + return arealine().x(x1).y(y0); + }; + + area.defined = function(_) { + return arguments.length ? (defined = typeof _ === "function" ? _ : constant$1(!!_), area) : defined; + }; + + area.curve = function(_) { + return arguments.length ? (curve = _, context != null && (output = curve(context)), area) : curve; + }; + + area.context = function(_) { + return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), area) : context; + }; + + return area; +} + +function descending$1(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; +} + +function identity$1(d) { + return d; +} + +function pie() { + var value = identity$1, + sortValues = descending$1, + sort = null, + startAngle = constant$1(0), + endAngle = constant$1(tau), + padAngle = constant$1(0); + + function pie(data) { + var i, + n = (data = array(data)).length, + j, + k, + sum = 0, + index = new Array(n), + arcs = new Array(n), + a0 = +startAngle.apply(this, arguments), + da = Math.min(tau, Math.max(-tau, endAngle.apply(this, arguments) - a0)), + a1, + p = Math.min(Math.abs(da) / n, padAngle.apply(this, arguments)), + pa = p * (da < 0 ? -1 : 1), + v; + + for (i = 0; i < n; ++i) { + if ((v = arcs[index[i] = i] = +value(data[i], i, data)) > 0) { + sum += v; + } + } + + // Optionally sort the arcs by previously-computed values or by data. + if (sortValues != null) index.sort(function(i, j) { return sortValues(arcs[i], arcs[j]); }); + else if (sort != null) index.sort(function(i, j) { return sort(data[i], data[j]); }); + + // Compute the arcs! They are stored in the original data's order. + for (i = 0, k = sum ? (da - n * pa) / sum : 0; i < n; ++i, a0 = a1) { + j = index[i], v = arcs[j], a1 = a0 + (v > 0 ? v * k : 0) + pa, arcs[j] = { + data: data[j], + index: i, + value: v, + startAngle: a0, + endAngle: a1, + padAngle: p + }; + } + + return arcs; + } + + pie.value = function(_) { + return arguments.length ? (value = typeof _ === "function" ? _ : constant$1(+_), pie) : value; + }; + + pie.sortValues = function(_) { + return arguments.length ? (sortValues = _, sort = null, pie) : sortValues; + }; + + pie.sort = function(_) { + return arguments.length ? (sort = _, sortValues = null, pie) : sort; + }; + + pie.startAngle = function(_) { + return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant$1(+_), pie) : startAngle; + }; + + pie.endAngle = function(_) { + return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant$1(+_), pie) : endAngle; + }; + + pie.padAngle = function(_) { + return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant$1(+_), pie) : padAngle; + }; + + return pie; +} + +var curveRadialLinear = curveRadial(curveLinear); + +function Radial(curve) { + this._curve = curve; +} + +Radial.prototype = { + areaStart: function() { + this._curve.areaStart(); + }, + areaEnd: function() { + this._curve.areaEnd(); + }, + lineStart: function() { + this._curve.lineStart(); + }, + lineEnd: function() { + this._curve.lineEnd(); + }, + point: function(a, r) { + this._curve.point(r * Math.sin(a), r * -Math.cos(a)); + } +}; + +function curveRadial(curve) { + + function radial(context) { + return new Radial(curve(context)); + } + + radial._curve = curve; + + return radial; +} + +function lineRadial(l) { + var c = l.curve; + + l.angle = l.x, delete l.x; + l.radius = l.y, delete l.y; + + l.curve = function(_) { + return arguments.length ? c(curveRadial(_)) : c()._curve; + }; + + return l; +} + +function lineRadial$1() { + return lineRadial(line().curve(curveRadialLinear)); +} + +function areaRadial() { + var a = area().curve(curveRadialLinear), + c = a.curve, + x0 = a.lineX0, + x1 = a.lineX1, + y0 = a.lineY0, + y1 = a.lineY1; + + a.angle = a.x, delete a.x; + a.startAngle = a.x0, delete a.x0; + a.endAngle = a.x1, delete a.x1; + a.radius = a.y, delete a.y; + a.innerRadius = a.y0, delete a.y0; + a.outerRadius = a.y1, delete a.y1; + a.lineStartAngle = function() { return lineRadial(x0()); }, delete a.lineX0; + a.lineEndAngle = function() { return lineRadial(x1()); }, delete a.lineX1; + a.lineInnerRadius = function() { return lineRadial(y0()); }, delete a.lineY0; + a.lineOuterRadius = function() { return lineRadial(y1()); }, delete a.lineY1; + + a.curve = function(_) { + return arguments.length ? c(curveRadial(_)) : c()._curve; + }; + + return a; +} + +function pointRadial(x, y) { + return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)]; +} + +class Bump { + constructor(context, x) { + this._context = context; + this._x = x; + } + areaStart() { + this._line = 0; + } + areaEnd() { + this._line = NaN; + } + lineStart() { + this._point = 0; + } + lineEnd() { + if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); + this._line = 1 - this._line; + } + point(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: { + this._point = 1; + if (this._line) this._context.lineTo(x, y); + else this._context.moveTo(x, y); + break; + } + case 1: this._point = 2; // falls through + default: { + if (this._x) this._context.bezierCurveTo(this._x0 = (this._x0 + x) / 2, this._y0, this._x0, y, x, y); + else this._context.bezierCurveTo(this._x0, this._y0 = (this._y0 + y) / 2, x, this._y0, x, y); + break; + } + } + this._x0 = x, this._y0 = y; + } +} + +class BumpRadial { + constructor(context) { + this._context = context; + } + lineStart() { + this._point = 0; + } + lineEnd() {} + point(x, y) { + x = +x, y = +y; + if (this._point === 0) { + this._point = 1; + } else { + const p0 = pointRadial(this._x0, this._y0); + const p1 = pointRadial(this._x0, this._y0 = (this._y0 + y) / 2); + const p2 = pointRadial(x, this._y0); + const p3 = pointRadial(x, y); + this._context.moveTo(...p0); + this._context.bezierCurveTo(...p1, ...p2, ...p3); + } + this._x0 = x, this._y0 = y; + } +} + +function bumpX(context) { + return new Bump(context, true); +} + +function bumpY(context) { + return new Bump(context, false); +} + +function bumpRadial(context) { + return new BumpRadial(context); +} + +function linkSource(d) { + return d.source; +} + +function linkTarget(d) { + return d.target; +} + +function link(curve) { + let source = linkSource, + target = linkTarget, + x$1 = x, + y$1 = y, + context = null, + output = null, + path = withPath(link); + + function link() { + let buffer; + const argv = slice.call(arguments); + const s = source.apply(this, argv); + const t = target.apply(this, argv); + if (context == null) output = curve(buffer = path()); + output.lineStart(); + argv[0] = s, output.point(+x$1.apply(this, argv), +y$1.apply(this, argv)); + argv[0] = t, output.point(+x$1.apply(this, argv), +y$1.apply(this, argv)); + output.lineEnd(); + if (buffer) return output = null, buffer + "" || null; + } + + link.source = function(_) { + return arguments.length ? (source = _, link) : source; + }; + + link.target = function(_) { + return arguments.length ? (target = _, link) : target; + }; + + link.x = function(_) { + return arguments.length ? (x$1 = typeof _ === "function" ? _ : constant$1(+_), link) : x$1; + }; + + link.y = function(_) { + return arguments.length ? (y$1 = typeof _ === "function" ? _ : constant$1(+_), link) : y$1; + }; + + link.context = function(_) { + return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), link) : context; + }; + + return link; +} + +function linkHorizontal() { + return link(bumpX); +} + +function linkVertical() { + return link(bumpY); +} + +function linkRadial() { + const l = link(bumpRadial); + l.angle = l.x, delete l.x; + l.radius = l.y, delete l.y; + return l; +} + +const sqrt3$2 = sqrt(3); + +var asterisk = { + draw(context, size) { + const r = sqrt(size + min(size / 28, 0.75)) * 0.59436; + const t = r / 2; + const u = t * sqrt3$2; + context.moveTo(0, r); + context.lineTo(0, -r); + context.moveTo(-u, -t); + context.lineTo(u, t); + context.moveTo(-u, t); + context.lineTo(u, -t); + } +}; + +var circle = { + draw(context, size) { + const r = sqrt(size / pi); + context.moveTo(r, 0); + context.arc(0, 0, r, 0, tau); + } +}; + +var cross = { + draw(context, size) { + const r = sqrt(size / 5) / 2; + context.moveTo(-3 * r, -r); + context.lineTo(-r, -r); + context.lineTo(-r, -3 * r); + context.lineTo(r, -3 * r); + context.lineTo(r, -r); + context.lineTo(3 * r, -r); + context.lineTo(3 * r, r); + context.lineTo(r, r); + context.lineTo(r, 3 * r); + context.lineTo(-r, 3 * r); + context.lineTo(-r, r); + context.lineTo(-3 * r, r); + context.closePath(); + } +}; + +const tan30 = sqrt(1 / 3); +const tan30_2 = tan30 * 2; + +var diamond = { + draw(context, size) { + const y = sqrt(size / tan30_2); + const x = y * tan30; + context.moveTo(0, -y); + context.lineTo(x, 0); + context.lineTo(0, y); + context.lineTo(-x, 0); + context.closePath(); + } +}; + +var diamond2 = { + draw(context, size) { + const r = sqrt(size) * 0.62625; + context.moveTo(0, -r); + context.lineTo(r, 0); + context.lineTo(0, r); + context.lineTo(-r, 0); + context.closePath(); + } +}; + +var plus = { + draw(context, size) { + const r = sqrt(size - min(size / 7, 2)) * 0.87559; + context.moveTo(-r, 0); + context.lineTo(r, 0); + context.moveTo(0, r); + context.lineTo(0, -r); + } +}; + +var square = { + draw(context, size) { + const w = sqrt(size); + const x = -w / 2; + context.rect(x, x, w, w); + } +}; + +var square2 = { + draw(context, size) { + const r = sqrt(size) * 0.4431; + context.moveTo(r, r); + context.lineTo(r, -r); + context.lineTo(-r, -r); + context.lineTo(-r, r); + context.closePath(); + } +}; + +const ka = 0.89081309152928522810; +const kr = sin(pi / 10) / sin(7 * pi / 10); +const kx = sin(tau / 10) * kr; +const ky = -cos(tau / 10) * kr; + +var star = { + draw(context, size) { + const r = sqrt(size * ka); + const x = kx * r; + const y = ky * r; + context.moveTo(0, -r); + context.lineTo(x, y); + for (let i = 1; i < 5; ++i) { + const a = tau * i / 5; + const c = cos(a); + const s = sin(a); + context.lineTo(s * r, -c * r); + context.lineTo(c * x - s * y, s * x + c * y); + } + context.closePath(); + } +}; + +const sqrt3$1 = sqrt(3); + +var triangle = { + draw(context, size) { + const y = -sqrt(size / (sqrt3$1 * 3)); + context.moveTo(0, y * 2); + context.lineTo(-sqrt3$1 * y, -y); + context.lineTo(sqrt3$1 * y, -y); + context.closePath(); + } +}; + +const sqrt3 = sqrt(3); + +var triangle2 = { + draw(context, size) { + const s = sqrt(size) * 0.6824; + const t = s / 2; + const u = (s * sqrt3) / 2; // cos(Math.PI / 6) + context.moveTo(0, -s); + context.lineTo(u, t); + context.lineTo(-u, t); + context.closePath(); + } +}; + +const c = -0.5; +const s = sqrt(3) / 2; +const k = 1 / sqrt(12); +const a = (k / 2 + 1) * 3; + +var wye = { + draw(context, size) { + const r = sqrt(size / a); + const x0 = r / 2, y0 = r * k; + const x1 = x0, y1 = r * k + r; + const x2 = -x1, y2 = y1; + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + context.lineTo(c * x0 - s * y0, s * x0 + c * y0); + context.lineTo(c * x1 - s * y1, s * x1 + c * y1); + context.lineTo(c * x2 - s * y2, s * x2 + c * y2); + context.lineTo(c * x0 + s * y0, c * y0 - s * x0); + context.lineTo(c * x1 + s * y1, c * y1 - s * x1); + context.lineTo(c * x2 + s * y2, c * y2 - s * x2); + context.closePath(); + } +}; + +var times = { + draw(context, size) { + const r = sqrt(size - min(size / 6, 1.7)) * 0.6189; + context.moveTo(-r, -r); + context.lineTo(r, r); + context.moveTo(-r, r); + context.lineTo(r, -r); + } +}; + +// These symbols are designed to be filled. +const symbolsFill = [ + circle, + cross, + diamond, + square, + star, + triangle, + wye +]; + +// These symbols are designed to be stroked (with a width of 1.5px and round caps). +const symbolsStroke = [ + circle, + plus, + times, + triangle2, + asterisk, + square2, + diamond2 +]; + +function Symbol$1(type, size) { + let context = null, + path = withPath(symbol); + + type = typeof type === "function" ? type : constant$1(type || circle); + size = typeof size === "function" ? size : constant$1(size === undefined ? 64 : +size); + + function symbol() { + let buffer; + if (!context) context = buffer = path(); + type.apply(this, arguments).draw(context, +size.apply(this, arguments)); + if (buffer) return context = null, buffer + "" || null; + } + + symbol.type = function(_) { + return arguments.length ? (type = typeof _ === "function" ? _ : constant$1(_), symbol) : type; + }; + + symbol.size = function(_) { + return arguments.length ? (size = typeof _ === "function" ? _ : constant$1(+_), symbol) : size; + }; + + symbol.context = function(_) { + return arguments.length ? (context = _ == null ? null : _, symbol) : context; + }; + + return symbol; +} + +function noop() {} + +function point$3(that, x, y) { + that._context.bezierCurveTo( + (2 * that._x0 + that._x1) / 3, + (2 * that._y0 + that._y1) / 3, + (that._x0 + 2 * that._x1) / 3, + (that._y0 + 2 * that._y1) / 3, + (that._x0 + 4 * that._x1 + x) / 6, + (that._y0 + 4 * that._y1 + y) / 6 + ); +} + +function Basis(context) { + this._context = context; +} + +Basis.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x0 = this._x1 = + this._y0 = this._y1 = NaN; + this._point = 0; + }, + lineEnd: function() { + switch (this._point) { + case 3: point$3(this, this._x1, this._y1); // falls through + case 2: this._context.lineTo(this._x1, this._y1); break; + } + if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); + this._line = 1 - this._line; + }, + point: function(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; + case 1: this._point = 2; break; + case 2: this._point = 3; this._context.lineTo((5 * this._x0 + this._x1) / 6, (5 * this._y0 + this._y1) / 6); // falls through + default: point$3(this, x, y); break; + } + this._x0 = this._x1, this._x1 = x; + this._y0 = this._y1, this._y1 = y; + } +}; + +function basis(context) { + return new Basis(context); +} + +function BasisClosed(context) { + this._context = context; +} + +BasisClosed.prototype = { + areaStart: noop, + areaEnd: noop, + lineStart: function() { + this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = + this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = NaN; + this._point = 0; + }, + lineEnd: function() { + switch (this._point) { + case 1: { + this._context.moveTo(this._x2, this._y2); + this._context.closePath(); + break; + } + case 2: { + this._context.moveTo((this._x2 + 2 * this._x3) / 3, (this._y2 + 2 * this._y3) / 3); + this._context.lineTo((this._x3 + 2 * this._x2) / 3, (this._y3 + 2 * this._y2) / 3); + this._context.closePath(); + break; + } + case 3: { + this.point(this._x2, this._y2); + this.point(this._x3, this._y3); + this.point(this._x4, this._y4); + break; + } + } + }, + point: function(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: this._point = 1; this._x2 = x, this._y2 = y; break; + case 1: this._point = 2; this._x3 = x, this._y3 = y; break; + case 2: this._point = 3; this._x4 = x, this._y4 = y; this._context.moveTo((this._x0 + 4 * this._x1 + x) / 6, (this._y0 + 4 * this._y1 + y) / 6); break; + default: point$3(this, x, y); break; + } + this._x0 = this._x1, this._x1 = x; + this._y0 = this._y1, this._y1 = y; + } +}; + +function basisClosed(context) { + return new BasisClosed(context); +} + +function BasisOpen(context) { + this._context = context; +} + +BasisOpen.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x0 = this._x1 = + this._y0 = this._y1 = NaN; + this._point = 0; + }, + lineEnd: function() { + if (this._line || (this._line !== 0 && this._point === 3)) this._context.closePath(); + this._line = 1 - this._line; + }, + point: function(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: this._point = 1; break; + case 1: this._point = 2; break; + case 2: this._point = 3; var x0 = (this._x0 + 4 * this._x1 + x) / 6, y0 = (this._y0 + 4 * this._y1 + y) / 6; this._line ? this._context.lineTo(x0, y0) : this._context.moveTo(x0, y0); break; + case 3: this._point = 4; // falls through + default: point$3(this, x, y); break; + } + this._x0 = this._x1, this._x1 = x; + this._y0 = this._y1, this._y1 = y; + } +}; + +function basisOpen(context) { + return new BasisOpen(context); +} + +function Bundle(context, beta) { + this._basis = new Basis(context); + this._beta = beta; +} + +Bundle.prototype = { + lineStart: function() { + this._x = []; + this._y = []; + this._basis.lineStart(); + }, + lineEnd: function() { + var x = this._x, + y = this._y, + j = x.length - 1; + + if (j > 0) { + var x0 = x[0], + y0 = y[0], + dx = x[j] - x0, + dy = y[j] - y0, + i = -1, + t; + + while (++i <= j) { + t = i / j; + this._basis.point( + this._beta * x[i] + (1 - this._beta) * (x0 + t * dx), + this._beta * y[i] + (1 - this._beta) * (y0 + t * dy) + ); + } + } + + this._x = this._y = null; + this._basis.lineEnd(); + }, + point: function(x, y) { + this._x.push(+x); + this._y.push(+y); + } +}; + +var bundle = (function custom(beta) { + + function bundle(context) { + return beta === 1 ? new Basis(context) : new Bundle(context, beta); + } + + bundle.beta = function(beta) { + return custom(+beta); + }; + + return bundle; +})(0.85); + +function point$2(that, x, y) { + that._context.bezierCurveTo( + that._x1 + that._k * (that._x2 - that._x0), + that._y1 + that._k * (that._y2 - that._y0), + that._x2 + that._k * (that._x1 - x), + that._y2 + that._k * (that._y1 - y), + that._x2, + that._y2 + ); +} + +function Cardinal(context, tension) { + this._context = context; + this._k = (1 - tension) / 6; +} + +Cardinal.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x0 = this._x1 = this._x2 = + this._y0 = this._y1 = this._y2 = NaN; + this._point = 0; + }, + lineEnd: function() { + switch (this._point) { + case 2: this._context.lineTo(this._x2, this._y2); break; + case 3: point$2(this, this._x1, this._y1); break; + } + if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); + this._line = 1 - this._line; + }, + point: function(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; + case 1: this._point = 2; this._x1 = x, this._y1 = y; break; + case 2: this._point = 3; // falls through + default: point$2(this, x, y); break; + } + this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; + this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; + } +}; + +var cardinal = (function custom(tension) { + + function cardinal(context) { + return new Cardinal(context, tension); + } + + cardinal.tension = function(tension) { + return custom(+tension); + }; + + return cardinal; +})(0); + +function CardinalClosed(context, tension) { + this._context = context; + this._k = (1 - tension) / 6; +} + +CardinalClosed.prototype = { + areaStart: noop, + areaEnd: noop, + lineStart: function() { + this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = this._x5 = + this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = this._y5 = NaN; + this._point = 0; + }, + lineEnd: function() { + switch (this._point) { + case 1: { + this._context.moveTo(this._x3, this._y3); + this._context.closePath(); + break; + } + case 2: { + this._context.lineTo(this._x3, this._y3); + this._context.closePath(); + break; + } + case 3: { + this.point(this._x3, this._y3); + this.point(this._x4, this._y4); + this.point(this._x5, this._y5); + break; + } + } + }, + point: function(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: this._point = 1; this._x3 = x, this._y3 = y; break; + case 1: this._point = 2; this._context.moveTo(this._x4 = x, this._y4 = y); break; + case 2: this._point = 3; this._x5 = x, this._y5 = y; break; + default: point$2(this, x, y); break; + } + this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; + this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; + } +}; + +var cardinalClosed = (function custom(tension) { + + function cardinal(context) { + return new CardinalClosed(context, tension); + } + + cardinal.tension = function(tension) { + return custom(+tension); + }; + + return cardinal; +})(0); + +function CardinalOpen(context, tension) { + this._context = context; + this._k = (1 - tension) / 6; +} + +CardinalOpen.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x0 = this._x1 = this._x2 = + this._y0 = this._y1 = this._y2 = NaN; + this._point = 0; + }, + lineEnd: function() { + if (this._line || (this._line !== 0 && this._point === 3)) this._context.closePath(); + this._line = 1 - this._line; + }, + point: function(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: this._point = 1; break; + case 1: this._point = 2; break; + case 2: this._point = 3; this._line ? this._context.lineTo(this._x2, this._y2) : this._context.moveTo(this._x2, this._y2); break; + case 3: this._point = 4; // falls through + default: point$2(this, x, y); break; + } + this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; + this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; + } +}; + +var cardinalOpen = (function custom(tension) { + + function cardinal(context) { + return new CardinalOpen(context, tension); + } + + cardinal.tension = function(tension) { + return custom(+tension); + }; + + return cardinal; +})(0); + +function point$1(that, x, y) { + var x1 = that._x1, + y1 = that._y1, + x2 = that._x2, + y2 = that._y2; + + if (that._l01_a > epsilon) { + var a = 2 * that._l01_2a + 3 * that._l01_a * that._l12_a + that._l12_2a, + n = 3 * that._l01_a * (that._l01_a + that._l12_a); + x1 = (x1 * a - that._x0 * that._l12_2a + that._x2 * that._l01_2a) / n; + y1 = (y1 * a - that._y0 * that._l12_2a + that._y2 * that._l01_2a) / n; + } + + if (that._l23_a > epsilon) { + var b = 2 * that._l23_2a + 3 * that._l23_a * that._l12_a + that._l12_2a, + m = 3 * that._l23_a * (that._l23_a + that._l12_a); + x2 = (x2 * b + that._x1 * that._l23_2a - x * that._l12_2a) / m; + y2 = (y2 * b + that._y1 * that._l23_2a - y * that._l12_2a) / m; + } + + that._context.bezierCurveTo(x1, y1, x2, y2, that._x2, that._y2); +} + +function CatmullRom(context, alpha) { + this._context = context; + this._alpha = alpha; +} + +CatmullRom.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x0 = this._x1 = this._x2 = + this._y0 = this._y1 = this._y2 = NaN; + this._l01_a = this._l12_a = this._l23_a = + this._l01_2a = this._l12_2a = this._l23_2a = + this._point = 0; + }, + lineEnd: function() { + switch (this._point) { + case 2: this._context.lineTo(this._x2, this._y2); break; + case 3: this.point(this._x2, this._y2); break; + } + if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); + this._line = 1 - this._line; + }, + point: function(x, y) { + x = +x, y = +y; + + if (this._point) { + var x23 = this._x2 - x, + y23 = this._y2 - y; + this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha)); + } + + switch (this._point) { + case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; + case 1: this._point = 2; break; + case 2: this._point = 3; // falls through + default: point$1(this, x, y); break; + } + + this._l01_a = this._l12_a, this._l12_a = this._l23_a; + this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a; + this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; + this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; + } +}; + +var catmullRom = (function custom(alpha) { + + function catmullRom(context) { + return alpha ? new CatmullRom(context, alpha) : new Cardinal(context, 0); + } + + catmullRom.alpha = function(alpha) { + return custom(+alpha); + }; + + return catmullRom; +})(0.5); + +function CatmullRomClosed(context, alpha) { + this._context = context; + this._alpha = alpha; +} + +CatmullRomClosed.prototype = { + areaStart: noop, + areaEnd: noop, + lineStart: function() { + this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = this._x5 = + this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = this._y5 = NaN; + this._l01_a = this._l12_a = this._l23_a = + this._l01_2a = this._l12_2a = this._l23_2a = + this._point = 0; + }, + lineEnd: function() { + switch (this._point) { + case 1: { + this._context.moveTo(this._x3, this._y3); + this._context.closePath(); + break; + } + case 2: { + this._context.lineTo(this._x3, this._y3); + this._context.closePath(); + break; + } + case 3: { + this.point(this._x3, this._y3); + this.point(this._x4, this._y4); + this.point(this._x5, this._y5); + break; + } + } + }, + point: function(x, y) { + x = +x, y = +y; + + if (this._point) { + var x23 = this._x2 - x, + y23 = this._y2 - y; + this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha)); + } + + switch (this._point) { + case 0: this._point = 1; this._x3 = x, this._y3 = y; break; + case 1: this._point = 2; this._context.moveTo(this._x4 = x, this._y4 = y); break; + case 2: this._point = 3; this._x5 = x, this._y5 = y; break; + default: point$1(this, x, y); break; + } + + this._l01_a = this._l12_a, this._l12_a = this._l23_a; + this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a; + this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; + this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; + } +}; + +var catmullRomClosed = (function custom(alpha) { + + function catmullRom(context) { + return alpha ? new CatmullRomClosed(context, alpha) : new CardinalClosed(context, 0); + } + + catmullRom.alpha = function(alpha) { + return custom(+alpha); + }; + + return catmullRom; +})(0.5); + +function CatmullRomOpen(context, alpha) { + this._context = context; + this._alpha = alpha; +} + +CatmullRomOpen.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x0 = this._x1 = this._x2 = + this._y0 = this._y1 = this._y2 = NaN; + this._l01_a = this._l12_a = this._l23_a = + this._l01_2a = this._l12_2a = this._l23_2a = + this._point = 0; + }, + lineEnd: function() { + if (this._line || (this._line !== 0 && this._point === 3)) this._context.closePath(); + this._line = 1 - this._line; + }, + point: function(x, y) { + x = +x, y = +y; + + if (this._point) { + var x23 = this._x2 - x, + y23 = this._y2 - y; + this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha)); + } + + switch (this._point) { + case 0: this._point = 1; break; + case 1: this._point = 2; break; + case 2: this._point = 3; this._line ? this._context.lineTo(this._x2, this._y2) : this._context.moveTo(this._x2, this._y2); break; + case 3: this._point = 4; // falls through + default: point$1(this, x, y); break; + } + + this._l01_a = this._l12_a, this._l12_a = this._l23_a; + this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a; + this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; + this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; + } +}; + +var catmullRomOpen = (function custom(alpha) { + + function catmullRom(context) { + return alpha ? new CatmullRomOpen(context, alpha) : new CardinalOpen(context, 0); + } + + catmullRom.alpha = function(alpha) { + return custom(+alpha); + }; + + return catmullRom; +})(0.5); + +function LinearClosed(context) { + this._context = context; +} + +LinearClosed.prototype = { + areaStart: noop, + areaEnd: noop, + lineStart: function() { + this._point = 0; + }, + lineEnd: function() { + if (this._point) this._context.closePath(); + }, + point: function(x, y) { + x = +x, y = +y; + if (this._point) this._context.lineTo(x, y); + else this._point = 1, this._context.moveTo(x, y); + } +}; + +function linearClosed(context) { + return new LinearClosed(context); +} + +function sign(x) { + return x < 0 ? -1 : 1; +} + +// Calculate the slopes of the tangents (Hermite-type interpolation) based on +// the following paper: Steffen, M. 1990. A Simple Method for Monotonic +// Interpolation in One Dimension. Astronomy and Astrophysics, Vol. 239, NO. +// NOV(II), P. 443, 1990. +function slope3(that, x2, y2) { + var h0 = that._x1 - that._x0, + h1 = x2 - that._x1, + s0 = (that._y1 - that._y0) / (h0 || h1 < 0 && -0), + s1 = (y2 - that._y1) / (h1 || h0 < 0 && -0), + p = (s0 * h1 + s1 * h0) / (h0 + h1); + return (sign(s0) + sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), 0.5 * Math.abs(p)) || 0; +} + +// Calculate a one-sided slope. +function slope2(that, t) { + var h = that._x1 - that._x0; + return h ? (3 * (that._y1 - that._y0) / h - t) / 2 : t; +} + +// According to https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Representations +// "you can express cubic Hermite interpolation in terms of cubic Bézier curves +// with respect to the four values p0, p0 + m0 / 3, p1 - m1 / 3, p1". +function point(that, t0, t1) { + var x0 = that._x0, + y0 = that._y0, + x1 = that._x1, + y1 = that._y1, + dx = (x1 - x0) / 3; + that._context.bezierCurveTo(x0 + dx, y0 + dx * t0, x1 - dx, y1 - dx * t1, x1, y1); +} + +function MonotoneX(context) { + this._context = context; +} + +MonotoneX.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x0 = this._x1 = + this._y0 = this._y1 = + this._t0 = NaN; + this._point = 0; + }, + lineEnd: function() { + switch (this._point) { + case 2: this._context.lineTo(this._x1, this._y1); break; + case 3: point(this, this._t0, slope2(this, this._t0)); break; + } + if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); + this._line = 1 - this._line; + }, + point: function(x, y) { + var t1 = NaN; + + x = +x, y = +y; + if (x === this._x1 && y === this._y1) return; // Ignore coincident points. + switch (this._point) { + case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; + case 1: this._point = 2; break; + case 2: this._point = 3; point(this, slope2(this, t1 = slope3(this, x, y)), t1); break; + default: point(this, this._t0, t1 = slope3(this, x, y)); break; + } + + this._x0 = this._x1, this._x1 = x; + this._y0 = this._y1, this._y1 = y; + this._t0 = t1; + } +}; + +function MonotoneY(context) { + this._context = new ReflectContext(context); +} + +(MonotoneY.prototype = Object.create(MonotoneX.prototype)).point = function(x, y) { + MonotoneX.prototype.point.call(this, y, x); +}; + +function ReflectContext(context) { + this._context = context; +} + +ReflectContext.prototype = { + moveTo: function(x, y) { this._context.moveTo(y, x); }, + closePath: function() { this._context.closePath(); }, + lineTo: function(x, y) { this._context.lineTo(y, x); }, + bezierCurveTo: function(x1, y1, x2, y2, x, y) { this._context.bezierCurveTo(y1, x1, y2, x2, y, x); } +}; + +function monotoneX(context) { + return new MonotoneX(context); +} + +function monotoneY(context) { + return new MonotoneY(context); +} + +function Natural(context) { + this._context = context; +} + +Natural.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x = []; + this._y = []; + }, + lineEnd: function() { + var x = this._x, + y = this._y, + n = x.length; + + if (n) { + this._line ? this._context.lineTo(x[0], y[0]) : this._context.moveTo(x[0], y[0]); + if (n === 2) { + this._context.lineTo(x[1], y[1]); + } else { + var px = controlPoints(x), + py = controlPoints(y); + for (var i0 = 0, i1 = 1; i1 < n; ++i0, ++i1) { + this._context.bezierCurveTo(px[0][i0], py[0][i0], px[1][i0], py[1][i0], x[i1], y[i1]); + } + } + } + + if (this._line || (this._line !== 0 && n === 1)) this._context.closePath(); + this._line = 1 - this._line; + this._x = this._y = null; + }, + point: function(x, y) { + this._x.push(+x); + this._y.push(+y); + } +}; + +// See https://www.particleincell.com/2012/bezier-splines/ for derivation. +function controlPoints(x) { + var i, + n = x.length - 1, + m, + a = new Array(n), + b = new Array(n), + r = new Array(n); + a[0] = 0, b[0] = 2, r[0] = x[0] + 2 * x[1]; + for (i = 1; i < n - 1; ++i) a[i] = 1, b[i] = 4, r[i] = 4 * x[i] + 2 * x[i + 1]; + a[n - 1] = 2, b[n - 1] = 7, r[n - 1] = 8 * x[n - 1] + x[n]; + for (i = 1; i < n; ++i) m = a[i] / b[i - 1], b[i] -= m, r[i] -= m * r[i - 1]; + a[n - 1] = r[n - 1] / b[n - 1]; + for (i = n - 2; i >= 0; --i) a[i] = (r[i] - a[i + 1]) / b[i]; + b[n - 1] = (x[n] + a[n - 1]) / 2; + for (i = 0; i < n - 1; ++i) b[i] = 2 * x[i + 1] - a[i + 1]; + return [a, b]; +} + +function natural(context) { + return new Natural(context); +} + +function Step(context, t) { + this._context = context; + this._t = t; +} + +Step.prototype = { + areaStart: function() { + this._line = 0; + }, + areaEnd: function() { + this._line = NaN; + }, + lineStart: function() { + this._x = this._y = NaN; + this._point = 0; + }, + lineEnd: function() { + if (0 < this._t && this._t < 1 && this._point === 2) this._context.lineTo(this._x, this._y); + if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); + if (this._line >= 0) this._t = 1 - this._t, this._line = 1 - this._line; + }, + point: function(x, y) { + x = +x, y = +y; + switch (this._point) { + case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; + case 1: this._point = 2; // falls through + default: { + if (this._t <= 0) { + this._context.lineTo(this._x, y); + this._context.lineTo(x, y); + } else { + var x1 = this._x * (1 - this._t) + x * this._t; + this._context.lineTo(x1, this._y); + this._context.lineTo(x1, y); + } + break; + } + } + this._x = x, this._y = y; + } +}; + +function step(context) { + return new Step(context, 0.5); +} + +function stepBefore(context) { + return new Step(context, 0); +} + +function stepAfter(context) { + return new Step(context, 1); +} + +function none$1(series, order) { + if (!((n = series.length) > 1)) return; + for (var i = 1, j, s0, s1 = series[order[0]], n, m = s1.length; i < n; ++i) { + s0 = s1, s1 = series[order[i]]; + for (j = 0; j < m; ++j) { + s1[j][1] += s1[j][0] = isNaN(s0[j][1]) ? s0[j][0] : s0[j][1]; + } + } +} + +function none(series) { + var n = series.length, o = new Array(n); + while (--n >= 0) o[n] = n; + return o; +} + +function stackValue(d, key) { + return d[key]; +} + +function stackSeries(key) { + const series = []; + series.key = key; + return series; +} + +function stack() { + var keys = constant$1([]), + order = none, + offset = none$1, + value = stackValue; + + function stack(data) { + var sz = Array.from(keys.apply(this, arguments), stackSeries), + i, n = sz.length, j = -1, + oz; + + for (const d of data) { + for (i = 0, ++j; i < n; ++i) { + (sz[i][j] = [0, +value(d, sz[i].key, j, data)]).data = d; + } + } + + for (i = 0, oz = array(order(sz)); i < n; ++i) { + sz[oz[i]].index = i; + } + + offset(sz, oz); + return sz; + } + + stack.keys = function(_) { + return arguments.length ? (keys = typeof _ === "function" ? _ : constant$1(Array.from(_)), stack) : keys; + }; + + stack.value = function(_) { + return arguments.length ? (value = typeof _ === "function" ? _ : constant$1(+_), stack) : value; + }; + + stack.order = function(_) { + return arguments.length ? (order = _ == null ? none : typeof _ === "function" ? _ : constant$1(Array.from(_)), stack) : order; + }; + + stack.offset = function(_) { + return arguments.length ? (offset = _ == null ? none$1 : _, stack) : offset; + }; + + return stack; +} + +function expand(series, order) { + if (!((n = series.length) > 0)) return; + for (var i, n, j = 0, m = series[0].length, y; j < m; ++j) { + for (y = i = 0; i < n; ++i) y += series[i][j][1] || 0; + if (y) for (i = 0; i < n; ++i) series[i][j][1] /= y; + } + none$1(series, order); +} + +function diverging(series, order) { + if (!((n = series.length) > 0)) return; + for (var i, j = 0, d, dy, yp, yn, n, m = series[order[0]].length; j < m; ++j) { + for (yp = yn = 0, i = 0; i < n; ++i) { + if ((dy = (d = series[order[i]][j])[1] - d[0]) > 0) { + d[0] = yp, d[1] = yp += dy; + } else if (dy < 0) { + d[1] = yn, d[0] = yn += dy; + } else { + d[0] = 0, d[1] = dy; + } + } + } +} + +function silhouette(series, order) { + if (!((n = series.length) > 0)) return; + for (var j = 0, s0 = series[order[0]], n, m = s0.length; j < m; ++j) { + for (var i = 0, y = 0; i < n; ++i) y += series[i][j][1] || 0; + s0[j][1] += s0[j][0] = -y / 2; + } + none$1(series, order); +} + +function wiggle(series, order) { + if (!((n = series.length) > 0) || !((m = (s0 = series[order[0]]).length) > 0)) return; + for (var y = 0, j = 1, s0, m, n; j < m; ++j) { + for (var i = 0, s1 = 0, s2 = 0; i < n; ++i) { + var si = series[order[i]], + sij0 = si[j][1] || 0, + sij1 = si[j - 1][1] || 0, + s3 = (sij0 - sij1) / 2; + for (var k = 0; k < i; ++k) { + var sk = series[order[k]], + skj0 = sk[j][1] || 0, + skj1 = sk[j - 1][1] || 0; + s3 += skj0 - skj1; + } + s1 += sij0, s2 += s3 * sij0; + } + s0[j - 1][1] += s0[j - 1][0] = y; + if (s1) y -= s2 / s1; + } + s0[j - 1][1] += s0[j - 1][0] = y; + none$1(series, order); +} + +function appearance(series) { + var peaks = series.map(peak); + return none(series).sort(function(a, b) { return peaks[a] - peaks[b]; }); +} + +function peak(series) { + var i = -1, j = 0, n = series.length, vi, vj = -Infinity; + while (++i < n) if ((vi = +series[i][1]) > vj) vj = vi, j = i; + return j; +} + +function ascending(series) { + var sums = series.map(sum); + return none(series).sort(function(a, b) { return sums[a] - sums[b]; }); +} + +function sum(series) { + var s = 0, i = -1, n = series.length, v; + while (++i < n) if (v = +series[i][1]) s += v; + return s; +} + +function descending(series) { + return ascending(series).reverse(); +} + +function insideOut(series) { + var n = series.length, + i, + j, + sums = series.map(sum), + order = appearance(series), + top = 0, + bottom = 0, + tops = [], + bottoms = []; + + for (i = 0; i < n; ++i) { + j = order[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + + return bottoms.reverse().concat(tops); +} + +function reverse(series) { + return none(series).reverse(); +} + +var constant = x => () => x; + +function ZoomEvent(type, { + sourceEvent, + target, + transform, + dispatch +}) { + Object.defineProperties(this, { + type: {value: type, enumerable: true, configurable: true}, + sourceEvent: {value: sourceEvent, enumerable: true, configurable: true}, + target: {value: target, enumerable: true, configurable: true}, + transform: {value: transform, enumerable: true, configurable: true}, + _: {value: dispatch} + }); +} + +function Transform(k, x, y) { + this.k = k; + this.x = x; + this.y = y; +} + +Transform.prototype = { + constructor: Transform, + scale: function(k) { + return k === 1 ? this : new Transform(this.k * k, this.x, this.y); + }, + translate: function(x, y) { + return x === 0 & y === 0 ? this : new Transform(this.k, this.x + this.k * x, this.y + this.k * y); + }, + apply: function(point) { + return [point[0] * this.k + this.x, point[1] * this.k + this.y]; + }, + applyX: function(x) { + return x * this.k + this.x; + }, + applyY: function(y) { + return y * this.k + this.y; + }, + invert: function(location) { + return [(location[0] - this.x) / this.k, (location[1] - this.y) / this.k]; + }, + invertX: function(x) { + return (x - this.x) / this.k; + }, + invertY: function(y) { + return (y - this.y) / this.k; + }, + rescaleX: function(x) { + return x.copy().domain(x.range().map(this.invertX, this).map(x.invert, x)); + }, + rescaleY: function(y) { + return y.copy().domain(y.range().map(this.invertY, this).map(y.invert, y)); + }, + toString: function() { + return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")"; + } +}; + +var identity = new Transform(1, 0, 0); + +transform.prototype = Transform.prototype; + +function transform(node) { + while (!node.__zoom) if (!(node = node.parentNode)) return identity; + return node.__zoom; +} + +function nopropagation(event) { + event.stopImmediatePropagation(); +} + +function noevent(event) { + event.preventDefault(); + event.stopImmediatePropagation(); +} + +// Ignore right-click, since that should open the context menu. +// except for pinch-to-zoom, which is sent as a wheel+ctrlKey event +function defaultFilter(event) { + return (!event.ctrlKey || event.type === 'wheel') && !event.button; +} + +function defaultExtent() { + var e = this; + if (e instanceof SVGElement) { + e = e.ownerSVGElement || e; + if (e.hasAttribute("viewBox")) { + e = e.viewBox.baseVal; + return [[e.x, e.y], [e.x + e.width, e.y + e.height]]; + } + return [[0, 0], [e.width.baseVal.value, e.height.baseVal.value]]; + } + return [[0, 0], [e.clientWidth, e.clientHeight]]; +} + +function defaultTransform() { + return this.__zoom || identity; +} + +function defaultWheelDelta(event) { + return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * (event.ctrlKey ? 10 : 1); +} + +function defaultTouchable() { + return navigator.maxTouchPoints || ("ontouchstart" in this); +} + +function defaultConstrain(transform, extent, translateExtent) { + var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0], + dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0], + dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1], + dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1]; + return transform.translate( + dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1), + dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1) + ); +} + +function zoom() { + var filter = defaultFilter, + extent = defaultExtent, + constrain = defaultConstrain, + wheelDelta = defaultWheelDelta, + touchable = defaultTouchable, + scaleExtent = [0, Infinity], + translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], + duration = 250, + interpolate = interpolateZoom, + listeners = dispatch("start", "zoom", "end"), + touchstarting, + touchfirst, + touchending, + touchDelay = 500, + wheelDelay = 150, + clickDistance2 = 0, + tapDistance = 10; + + function zoom(selection) { + selection + .property("__zoom", defaultTransform) + .on("wheel.zoom", wheeled, {passive: false}) + .on("mousedown.zoom", mousedowned) + .on("dblclick.zoom", dblclicked) + .filter(touchable) + .on("touchstart.zoom", touchstarted) + .on("touchmove.zoom", touchmoved) + .on("touchend.zoom touchcancel.zoom", touchended) + .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)"); + } + + zoom.transform = function(collection, transform, point, event) { + var selection = collection.selection ? collection.selection() : collection; + selection.property("__zoom", defaultTransform); + if (collection !== selection) { + schedule(collection, transform, point, event); + } else { + selection.interrupt().each(function() { + gesture(this, arguments) + .event(event) + .start() + .zoom(null, typeof transform === "function" ? transform.apply(this, arguments) : transform) + .end(); + }); + } + }; + + zoom.scaleBy = function(selection, k, p, event) { + zoom.scaleTo(selection, function() { + var k0 = this.__zoom.k, + k1 = typeof k === "function" ? k.apply(this, arguments) : k; + return k0 * k1; + }, p, event); + }; + + zoom.scaleTo = function(selection, k, p, event) { + zoom.transform(selection, function() { + var e = extent.apply(this, arguments), + t0 = this.__zoom, + p0 = p == null ? centroid(e) : typeof p === "function" ? p.apply(this, arguments) : p, + p1 = t0.invert(p0), + k1 = typeof k === "function" ? k.apply(this, arguments) : k; + return constrain(translate(scale(t0, k1), p0, p1), e, translateExtent); + }, p, event); + }; + + zoom.translateBy = function(selection, x, y, event) { + zoom.transform(selection, function() { + return constrain(this.__zoom.translate( + typeof x === "function" ? x.apply(this, arguments) : x, + typeof y === "function" ? y.apply(this, arguments) : y + ), extent.apply(this, arguments), translateExtent); + }, null, event); + }; + + zoom.translateTo = function(selection, x, y, p, event) { + zoom.transform(selection, function() { + var e = extent.apply(this, arguments), + t = this.__zoom, + p0 = p == null ? centroid(e) : typeof p === "function" ? p.apply(this, arguments) : p; + return constrain(identity.translate(p0[0], p0[1]).scale(t.k).translate( + typeof x === "function" ? -x.apply(this, arguments) : -x, + typeof y === "function" ? -y.apply(this, arguments) : -y + ), e, translateExtent); + }, p, event); + }; + + function scale(transform, k) { + k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], k)); + return k === transform.k ? transform : new Transform(k, transform.x, transform.y); + } + + function translate(transform, p0, p1) { + var x = p0[0] - p1[0] * transform.k, y = p0[1] - p1[1] * transform.k; + return x === transform.x && y === transform.y ? transform : new Transform(transform.k, x, y); + } + + function centroid(extent) { + return [(+extent[0][0] + +extent[1][0]) / 2, (+extent[0][1] + +extent[1][1]) / 2]; + } + + function schedule(transition, transform, point, event) { + transition + .on("start.zoom", function() { gesture(this, arguments).event(event).start(); }) + .on("interrupt.zoom end.zoom", function() { gesture(this, arguments).event(event).end(); }) + .tween("zoom", function() { + var that = this, + args = arguments, + g = gesture(that, args).event(event), + e = extent.apply(that, args), + p = point == null ? centroid(e) : typeof point === "function" ? point.apply(that, args) : point, + w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]), + a = that.__zoom, + b = typeof transform === "function" ? transform.apply(that, args) : transform, + i = interpolate(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k)); + return function(t) { + if (t === 1) t = b; // Avoid rounding error on end. + else { var l = i(t), k = w / l[2]; t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); } + g.zoom(null, t); + }; + }); + } + + function gesture(that, args, clean) { + return (!clean && that.__zooming) || new Gesture(that, args); + } + + function Gesture(that, args) { + this.that = that; + this.args = args; + this.active = 0; + this.sourceEvent = null; + this.extent = extent.apply(that, args); + this.taps = 0; + } + + Gesture.prototype = { + event: function(event) { + if (event) this.sourceEvent = event; + return this; + }, + start: function() { + if (++this.active === 1) { + this.that.__zooming = this; + this.emit("start"); + } + return this; + }, + zoom: function(key, transform) { + if (this.mouse && key !== "mouse") this.mouse[1] = transform.invert(this.mouse[0]); + if (this.touch0 && key !== "touch") this.touch0[1] = transform.invert(this.touch0[0]); + if (this.touch1 && key !== "touch") this.touch1[1] = transform.invert(this.touch1[0]); + this.that.__zoom = transform; + this.emit("zoom"); + return this; + }, + end: function() { + if (--this.active === 0) { + delete this.that.__zooming; + this.emit("end"); + } + return this; + }, + emit: function(type) { + var d = select(this.that).datum(); + listeners.call( + type, + this.that, + new ZoomEvent(type, { + sourceEvent: this.sourceEvent, + target: zoom, + type, + transform: this.that.__zoom, + dispatch: listeners + }), + d + ); + } + }; + + function wheeled(event, ...args) { + if (!filter.apply(this, arguments)) return; + var g = gesture(this, args).event(event), + t = this.__zoom, + k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k * Math.pow(2, wheelDelta.apply(this, arguments)))), + p = pointer(event); + + // If the mouse is in the same location as before, reuse it. + // If there were recent wheel events, reset the wheel idle timeout. + if (g.wheel) { + if (g.mouse[0][0] !== p[0] || g.mouse[0][1] !== p[1]) { + g.mouse[1] = t.invert(g.mouse[0] = p); + } + clearTimeout(g.wheel); + } + + // If this wheel event won’t trigger a transform change, ignore it. + else if (t.k === k) return; + + // Otherwise, capture the mouse point and location at the start. + else { + g.mouse = [p, t.invert(p)]; + interrupt(this); + g.start(); + } + + noevent(event); + g.wheel = setTimeout(wheelidled, wheelDelay); + g.zoom("mouse", constrain(translate(scale(t, k), g.mouse[0], g.mouse[1]), g.extent, translateExtent)); + + function wheelidled() { + g.wheel = null; + g.end(); + } + } + + function mousedowned(event, ...args) { + if (touchending || !filter.apply(this, arguments)) return; + var currentTarget = event.currentTarget, + g = gesture(this, args, true).event(event), + v = select(event.view).on("mousemove.zoom", mousemoved, true).on("mouseup.zoom", mouseupped, true), + p = pointer(event, currentTarget), + x0 = event.clientX, + y0 = event.clientY; + + dragDisable(event.view); + nopropagation(event); + g.mouse = [p, this.__zoom.invert(p)]; + interrupt(this); + g.start(); + + function mousemoved(event) { + noevent(event); + if (!g.moved) { + var dx = event.clientX - x0, dy = event.clientY - y0; + g.moved = dx * dx + dy * dy > clickDistance2; + } + g.event(event) + .zoom("mouse", constrain(translate(g.that.__zoom, g.mouse[0] = pointer(event, currentTarget), g.mouse[1]), g.extent, translateExtent)); + } + + function mouseupped(event) { + v.on("mousemove.zoom mouseup.zoom", null); + yesdrag(event.view, g.moved); + noevent(event); + g.event(event).end(); + } + } + + function dblclicked(event, ...args) { + if (!filter.apply(this, arguments)) return; + var t0 = this.__zoom, + p0 = pointer(event.changedTouches ? event.changedTouches[0] : event, this), + p1 = t0.invert(p0), + k1 = t0.k * (event.shiftKey ? 0.5 : 2), + t1 = constrain(translate(scale(t0, k1), p0, p1), extent.apply(this, args), translateExtent); + + noevent(event); + if (duration > 0) select(this).transition().duration(duration).call(schedule, t1, p0, event); + else select(this).call(zoom.transform, t1, p0, event); + } + + function touchstarted(event, ...args) { + if (!filter.apply(this, arguments)) return; + var touches = event.touches, + n = touches.length, + g = gesture(this, args, event.changedTouches.length === n).event(event), + started, i, t, p; + + nopropagation(event); + for (i = 0; i < n; ++i) { + t = touches[i], p = pointer(t, this); + p = [p, this.__zoom.invert(p), t.identifier]; + if (!g.touch0) g.touch0 = p, started = true, g.taps = 1 + !!touchstarting; + else if (!g.touch1 && g.touch0[2] !== p[2]) g.touch1 = p, g.taps = 0; + } + + if (touchstarting) touchstarting = clearTimeout(touchstarting); + + if (started) { + if (g.taps < 2) touchfirst = p[0], touchstarting = setTimeout(function() { touchstarting = null; }, touchDelay); + interrupt(this); + g.start(); + } + } + + function touchmoved(event, ...args) { + if (!this.__zooming) return; + var g = gesture(this, args).event(event), + touches = event.changedTouches, + n = touches.length, i, t, p, l; + + noevent(event); + for (i = 0; i < n; ++i) { + t = touches[i], p = pointer(t, this); + if (g.touch0 && g.touch0[2] === t.identifier) g.touch0[0] = p; + else if (g.touch1 && g.touch1[2] === t.identifier) g.touch1[0] = p; + } + t = g.that.__zoom; + if (g.touch1) { + var p0 = g.touch0[0], l0 = g.touch0[1], + p1 = g.touch1[0], l1 = g.touch1[1], + dp = (dp = p1[0] - p0[0]) * dp + (dp = p1[1] - p0[1]) * dp, + dl = (dl = l1[0] - l0[0]) * dl + (dl = l1[1] - l0[1]) * dl; + t = scale(t, Math.sqrt(dp / dl)); + p = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2]; + l = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2]; + } + else if (g.touch0) p = g.touch0[0], l = g.touch0[1]; + else return; + + g.zoom("touch", constrain(translate(t, p, l), g.extent, translateExtent)); + } + + function touchended(event, ...args) { + if (!this.__zooming) return; + var g = gesture(this, args).event(event), + touches = event.changedTouches, + n = touches.length, i, t; + + nopropagation(event); + if (touchending) clearTimeout(touchending); + touchending = setTimeout(function() { touchending = null; }, touchDelay); + for (i = 0; i < n; ++i) { + t = touches[i]; + if (g.touch0 && g.touch0[2] === t.identifier) delete g.touch0; + else if (g.touch1 && g.touch1[2] === t.identifier) delete g.touch1; + } + if (g.touch1 && !g.touch0) g.touch0 = g.touch1, delete g.touch1; + if (g.touch0) g.touch0[1] = this.__zoom.invert(g.touch0[0]); + else { + g.end(); + // If this was a dbltap, reroute to the (optional) dblclick.zoom handler. + if (g.taps === 2) { + t = pointer(t, this); + if (Math.hypot(touchfirst[0] - t[0], touchfirst[1] - t[1]) < tapDistance) { + var p = select(this).on("dblclick.zoom"); + if (p) p.apply(this, arguments); + } + } + } + } + + zoom.wheelDelta = function(_) { + return arguments.length ? (wheelDelta = typeof _ === "function" ? _ : constant(+_), zoom) : wheelDelta; + }; + + zoom.filter = function(_) { + return arguments.length ? (filter = typeof _ === "function" ? _ : constant(!!_), zoom) : filter; + }; + + zoom.touchable = function(_) { + return arguments.length ? (touchable = typeof _ === "function" ? _ : constant(!!_), zoom) : touchable; + }; + + zoom.extent = function(_) { + return arguments.length ? (extent = typeof _ === "function" ? _ : constant([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), zoom) : extent; + }; + + zoom.scaleExtent = function(_) { + return arguments.length ? (scaleExtent[0] = +_[0], scaleExtent[1] = +_[1], zoom) : [scaleExtent[0], scaleExtent[1]]; + }; + + zoom.translateExtent = function(_) { + return arguments.length ? (translateExtent[0][0] = +_[0][0], translateExtent[1][0] = +_[1][0], translateExtent[0][1] = +_[0][1], translateExtent[1][1] = +_[1][1], zoom) : [[translateExtent[0][0], translateExtent[0][1]], [translateExtent[1][0], translateExtent[1][1]]]; + }; + + zoom.constrain = function(_) { + return arguments.length ? (constrain = _, zoom) : constrain; + }; + + zoom.duration = function(_) { + return arguments.length ? (duration = +_, zoom) : duration; + }; + + zoom.interpolate = function(_) { + return arguments.length ? (interpolate = _, zoom) : interpolate; + }; + + zoom.on = function() { + var value = listeners.on.apply(listeners, arguments); + return value === listeners ? zoom : value; + }; + + zoom.clickDistance = function(_) { + return arguments.length ? (clickDistance2 = (_ = +_) * _, zoom) : Math.sqrt(clickDistance2); + }; + + zoom.tapDistance = function(_) { + return arguments.length ? (tapDistance = +_, zoom) : tapDistance; + }; + + return zoom; +} + +exports.Adder = Adder; +exports.Delaunay = Delaunay; +exports.FormatSpecifier = FormatSpecifier; +exports.InternMap = InternMap; +exports.InternSet = InternSet; +exports.Node = Node$1; +exports.Path = Path$1; +exports.Voronoi = Voronoi; +exports.ZoomTransform = Transform; +exports.active = active; +exports.arc = arc; +exports.area = area; +exports.areaRadial = areaRadial; +exports.ascending = ascending$3; +exports.autoType = autoType; +exports.axisBottom = axisBottom; +exports.axisLeft = axisLeft; +exports.axisRight = axisRight; +exports.axisTop = axisTop; +exports.bin = bin; +exports.bisect = bisect; +exports.bisectCenter = bisectCenter; +exports.bisectLeft = bisectLeft; +exports.bisectRight = bisectRight; +exports.bisector = bisector; +exports.blob = blob; +exports.blur = blur; +exports.blur2 = blur2; +exports.blurImage = blurImage; +exports.brush = brush; +exports.brushSelection = brushSelection; +exports.brushX = brushX; +exports.brushY = brushY; +exports.buffer = buffer; +exports.chord = chord; +exports.chordDirected = chordDirected; +exports.chordTranspose = chordTranspose; +exports.cluster = cluster; +exports.color = color; +exports.contourDensity = density; +exports.contours = Contours; +exports.count = count$1; +exports.create = create$1; +exports.creator = creator; +exports.cross = cross$2; +exports.csv = csv; +exports.csvFormat = csvFormat; +exports.csvFormatBody = csvFormatBody; +exports.csvFormatRow = csvFormatRow; +exports.csvFormatRows = csvFormatRows; +exports.csvFormatValue = csvFormatValue; +exports.csvParse = csvParse; +exports.csvParseRows = csvParseRows; +exports.cubehelix = cubehelix$3; +exports.cumsum = cumsum; +exports.curveBasis = basis; +exports.curveBasisClosed = basisClosed; +exports.curveBasisOpen = basisOpen; +exports.curveBumpX = bumpX; +exports.curveBumpY = bumpY; +exports.curveBundle = bundle; +exports.curveCardinal = cardinal; +exports.curveCardinalClosed = cardinalClosed; +exports.curveCardinalOpen = cardinalOpen; +exports.curveCatmullRom = catmullRom; +exports.curveCatmullRomClosed = catmullRomClosed; +exports.curveCatmullRomOpen = catmullRomOpen; +exports.curveLinear = curveLinear; +exports.curveLinearClosed = linearClosed; +exports.curveMonotoneX = monotoneX; +exports.curveMonotoneY = monotoneY; +exports.curveNatural = natural; +exports.curveStep = step; +exports.curveStepAfter = stepAfter; +exports.curveStepBefore = stepBefore; +exports.descending = descending$2; +exports.deviation = deviation; +exports.difference = difference; +exports.disjoint = disjoint; +exports.dispatch = dispatch; +exports.drag = drag; +exports.dragDisable = dragDisable; +exports.dragEnable = yesdrag; +exports.dsv = dsv; +exports.dsvFormat = dsvFormat; +exports.easeBack = backInOut; +exports.easeBackIn = backIn; +exports.easeBackInOut = backInOut; +exports.easeBackOut = backOut; +exports.easeBounce = bounceOut; +exports.easeBounceIn = bounceIn; +exports.easeBounceInOut = bounceInOut; +exports.easeBounceOut = bounceOut; +exports.easeCircle = circleInOut; +exports.easeCircleIn = circleIn; +exports.easeCircleInOut = circleInOut; +exports.easeCircleOut = circleOut; +exports.easeCubic = cubicInOut; +exports.easeCubicIn = cubicIn; +exports.easeCubicInOut = cubicInOut; +exports.easeCubicOut = cubicOut; +exports.easeElastic = elasticOut; +exports.easeElasticIn = elasticIn; +exports.easeElasticInOut = elasticInOut; +exports.easeElasticOut = elasticOut; +exports.easeExp = expInOut; +exports.easeExpIn = expIn; +exports.easeExpInOut = expInOut; +exports.easeExpOut = expOut; +exports.easeLinear = linear$1; +exports.easePoly = polyInOut; +exports.easePolyIn = polyIn; +exports.easePolyInOut = polyInOut; +exports.easePolyOut = polyOut; +exports.easeQuad = quadInOut; +exports.easeQuadIn = quadIn; +exports.easeQuadInOut = quadInOut; +exports.easeQuadOut = quadOut; +exports.easeSin = sinInOut; +exports.easeSinIn = sinIn; +exports.easeSinInOut = sinInOut; +exports.easeSinOut = sinOut; +exports.every = every; +exports.extent = extent$1; +exports.fcumsum = fcumsum; +exports.filter = filter$1; +exports.flatGroup = flatGroup; +exports.flatRollup = flatRollup; +exports.forceCenter = center; +exports.forceCollide = collide; +exports.forceLink = link$2; +exports.forceManyBody = manyBody; +exports.forceRadial = radial$1; +exports.forceSimulation = simulation; +exports.forceX = x$1; +exports.forceY = y$1; +exports.formatDefaultLocale = defaultLocale$1; +exports.formatLocale = formatLocale$1; +exports.formatSpecifier = formatSpecifier; +exports.fsum = fsum; +exports.geoAlbers = albers; +exports.geoAlbersUsa = albersUsa; +exports.geoArea = area$2; +exports.geoAzimuthalEqualArea = azimuthalEqualArea; +exports.geoAzimuthalEqualAreaRaw = azimuthalEqualAreaRaw; +exports.geoAzimuthalEquidistant = azimuthalEquidistant; +exports.geoAzimuthalEquidistantRaw = azimuthalEquidistantRaw; +exports.geoBounds = bounds; +exports.geoCentroid = centroid$1; +exports.geoCircle = circle$1; +exports.geoClipAntimeridian = clipAntimeridian; +exports.geoClipCircle = clipCircle; +exports.geoClipExtent = extent; +exports.geoClipRectangle = clipRectangle; +exports.geoConicConformal = conicConformal; +exports.geoConicConformalRaw = conicConformalRaw; +exports.geoConicEqualArea = conicEqualArea; +exports.geoConicEqualAreaRaw = conicEqualAreaRaw; +exports.geoConicEquidistant = conicEquidistant; +exports.geoConicEquidistantRaw = conicEquidistantRaw; +exports.geoContains = contains$1; +exports.geoDistance = distance; +exports.geoEqualEarth = equalEarth; +exports.geoEqualEarthRaw = equalEarthRaw; +exports.geoEquirectangular = equirectangular; +exports.geoEquirectangularRaw = equirectangularRaw; +exports.geoGnomonic = gnomonic; +exports.geoGnomonicRaw = gnomonicRaw; +exports.geoGraticule = graticule; +exports.geoGraticule10 = graticule10; +exports.geoIdentity = identity$4; +exports.geoInterpolate = interpolate; +exports.geoLength = length$1; +exports.geoMercator = mercator; +exports.geoMercatorRaw = mercatorRaw; +exports.geoNaturalEarth1 = naturalEarth1; +exports.geoNaturalEarth1Raw = naturalEarth1Raw; +exports.geoOrthographic = orthographic; +exports.geoOrthographicRaw = orthographicRaw; +exports.geoPath = index$2; +exports.geoProjection = projection; +exports.geoProjectionMutator = projectionMutator; +exports.geoRotation = rotation; +exports.geoStereographic = stereographic; +exports.geoStereographicRaw = stereographicRaw; +exports.geoStream = geoStream; +exports.geoTransform = transform$1; +exports.geoTransverseMercator = transverseMercator; +exports.geoTransverseMercatorRaw = transverseMercatorRaw; +exports.gray = gray; +exports.greatest = greatest; +exports.greatestIndex = greatestIndex; +exports.group = group; +exports.groupSort = groupSort; +exports.groups = groups; +exports.hcl = hcl$2; +exports.hierarchy = hierarchy; +exports.histogram = bin; +exports.hsl = hsl$2; +exports.html = html; +exports.image = image; +exports.index = index$4; +exports.indexes = indexes; +exports.interpolate = interpolate$2; +exports.interpolateArray = array$3; +exports.interpolateBasis = basis$2; +exports.interpolateBasisClosed = basisClosed$1; +exports.interpolateBlues = Blues; +exports.interpolateBrBG = BrBG; +exports.interpolateBuGn = BuGn; +exports.interpolateBuPu = BuPu; +exports.interpolateCividis = cividis; +exports.interpolateCool = cool; +exports.interpolateCubehelix = cubehelix$2; +exports.interpolateCubehelixDefault = cubehelix; +exports.interpolateCubehelixLong = cubehelixLong; +exports.interpolateDate = date$1; +exports.interpolateDiscrete = discrete; +exports.interpolateGnBu = GnBu; +exports.interpolateGreens = Greens; +exports.interpolateGreys = Greys; +exports.interpolateHcl = hcl$1; +exports.interpolateHclLong = hclLong; +exports.interpolateHsl = hsl$1; +exports.interpolateHslLong = hslLong; +exports.interpolateHue = hue; +exports.interpolateInferno = inferno; +exports.interpolateLab = lab; +exports.interpolateMagma = magma; +exports.interpolateNumber = interpolateNumber; +exports.interpolateNumberArray = numberArray; +exports.interpolateObject = object$1; +exports.interpolateOrRd = OrRd; +exports.interpolateOranges = Oranges; +exports.interpolatePRGn = PRGn; +exports.interpolatePiYG = PiYG; +exports.interpolatePlasma = plasma; +exports.interpolatePuBu = PuBu; +exports.interpolatePuBuGn = PuBuGn; +exports.interpolatePuOr = PuOr; +exports.interpolatePuRd = PuRd; +exports.interpolatePurples = Purples; +exports.interpolateRainbow = rainbow; +exports.interpolateRdBu = RdBu; +exports.interpolateRdGy = RdGy; +exports.interpolateRdPu = RdPu; +exports.interpolateRdYlBu = RdYlBu; +exports.interpolateRdYlGn = RdYlGn; +exports.interpolateReds = Reds; +exports.interpolateRgb = interpolateRgb; +exports.interpolateRgbBasis = rgbBasis; +exports.interpolateRgbBasisClosed = rgbBasisClosed; +exports.interpolateRound = interpolateRound; +exports.interpolateSinebow = sinebow; +exports.interpolateSpectral = Spectral; +exports.interpolateString = interpolateString; +exports.interpolateTransformCss = interpolateTransformCss; +exports.interpolateTransformSvg = interpolateTransformSvg; +exports.interpolateTurbo = turbo; +exports.interpolateViridis = viridis; +exports.interpolateWarm = warm; +exports.interpolateYlGn = YlGn; +exports.interpolateYlGnBu = YlGnBu; +exports.interpolateYlOrBr = YlOrBr; +exports.interpolateYlOrRd = YlOrRd; +exports.interpolateZoom = interpolateZoom; +exports.interrupt = interrupt; +exports.intersection = intersection; +exports.interval = interval; +exports.isoFormat = formatIso$1; +exports.isoParse = parseIso$1; +exports.json = json; +exports.lab = lab$1; +exports.lch = lch; +exports.least = least; +exports.leastIndex = leastIndex; +exports.line = line; +exports.lineRadial = lineRadial$1; +exports.link = link; +exports.linkHorizontal = linkHorizontal; +exports.linkRadial = linkRadial; +exports.linkVertical = linkVertical; +exports.local = local$1; +exports.map = map$1; +exports.matcher = matcher; +exports.max = max$3; +exports.maxIndex = maxIndex; +exports.mean = mean; +exports.median = median; +exports.medianIndex = medianIndex; +exports.merge = merge; +exports.min = min$2; +exports.minIndex = minIndex; +exports.mode = mode; +exports.namespace = namespace; +exports.namespaces = namespaces; +exports.nice = nice$1; +exports.now = now; +exports.pack = index$1; +exports.packEnclose = enclose; +exports.packSiblings = siblings; +exports.pairs = pairs; +exports.partition = partition; +exports.path = path; +exports.pathRound = pathRound; +exports.permute = permute; +exports.pie = pie; +exports.piecewise = piecewise; +exports.pointRadial = pointRadial; +exports.pointer = pointer; +exports.pointers = pointers; +exports.polygonArea = area$1; +exports.polygonCentroid = centroid; +exports.polygonContains = contains; +exports.polygonHull = hull; +exports.polygonLength = length; +exports.precisionFixed = precisionFixed; +exports.precisionPrefix = precisionPrefix; +exports.precisionRound = precisionRound; +exports.quadtree = quadtree; +exports.quantile = quantile$1; +exports.quantileIndex = quantileIndex; +exports.quantileSorted = quantileSorted; +exports.quantize = quantize$1; +exports.quickselect = quickselect; +exports.radialArea = areaRadial; +exports.radialLine = lineRadial$1; +exports.randomBates = bates; +exports.randomBernoulli = bernoulli; +exports.randomBeta = beta; +exports.randomBinomial = binomial; +exports.randomCauchy = cauchy; +exports.randomExponential = exponential; +exports.randomGamma = gamma; +exports.randomGeometric = geometric; +exports.randomInt = int; +exports.randomIrwinHall = irwinHall; +exports.randomLcg = lcg; +exports.randomLogNormal = logNormal; +exports.randomLogistic = logistic; +exports.randomNormal = normal; +exports.randomPareto = pareto; +exports.randomPoisson = poisson; +exports.randomUniform = uniform; +exports.randomWeibull = weibull; +exports.range = range$2; +exports.rank = rank; +exports.reduce = reduce; +exports.reverse = reverse$1; +exports.rgb = rgb; +exports.ribbon = ribbon$1; +exports.ribbonArrow = ribbonArrow; +exports.rollup = rollup; +exports.rollups = rollups; +exports.scaleBand = band; +exports.scaleDiverging = diverging$1; +exports.scaleDivergingLog = divergingLog; +exports.scaleDivergingPow = divergingPow; +exports.scaleDivergingSqrt = divergingSqrt; +exports.scaleDivergingSymlog = divergingSymlog; +exports.scaleIdentity = identity$2; +exports.scaleImplicit = implicit; +exports.scaleLinear = linear; +exports.scaleLog = log; +exports.scaleOrdinal = ordinal; +exports.scalePoint = point$4; +exports.scalePow = pow; +exports.scaleQuantile = quantile; +exports.scaleQuantize = quantize; +exports.scaleRadial = radial; +exports.scaleSequential = sequential; +exports.scaleSequentialLog = sequentialLog; +exports.scaleSequentialPow = sequentialPow; +exports.scaleSequentialQuantile = sequentialQuantile; +exports.scaleSequentialSqrt = sequentialSqrt; +exports.scaleSequentialSymlog = sequentialSymlog; +exports.scaleSqrt = sqrt$1; +exports.scaleSymlog = symlog; +exports.scaleThreshold = threshold; +exports.scaleTime = time; +exports.scaleUtc = utcTime; +exports.scan = scan; +exports.schemeAccent = Accent; +exports.schemeBlues = scheme$5; +exports.schemeBrBG = scheme$q; +exports.schemeBuGn = scheme$h; +exports.schemeBuPu = scheme$g; +exports.schemeCategory10 = category10; +exports.schemeDark2 = Dark2; +exports.schemeGnBu = scheme$f; +exports.schemeGreens = scheme$4; +exports.schemeGreys = scheme$3; +exports.schemeOrRd = scheme$e; +exports.schemeOranges = scheme; +exports.schemePRGn = scheme$p; +exports.schemePaired = Paired; +exports.schemePastel1 = Pastel1; +exports.schemePastel2 = Pastel2; +exports.schemePiYG = scheme$o; +exports.schemePuBu = scheme$c; +exports.schemePuBuGn = scheme$d; +exports.schemePuOr = scheme$n; +exports.schemePuRd = scheme$b; +exports.schemePurples = scheme$2; +exports.schemeRdBu = scheme$m; +exports.schemeRdGy = scheme$l; +exports.schemeRdPu = scheme$a; +exports.schemeRdYlBu = scheme$k; +exports.schemeRdYlGn = scheme$j; +exports.schemeReds = scheme$1; +exports.schemeSet1 = Set1; +exports.schemeSet2 = Set2; +exports.schemeSet3 = Set3; +exports.schemeSpectral = scheme$i; +exports.schemeTableau10 = Tableau10; +exports.schemeYlGn = scheme$8; +exports.schemeYlGnBu = scheme$9; +exports.schemeYlOrBr = scheme$7; +exports.schemeYlOrRd = scheme$6; +exports.select = select; +exports.selectAll = selectAll; +exports.selection = selection; +exports.selector = selector; +exports.selectorAll = selectorAll; +exports.shuffle = shuffle$1; +exports.shuffler = shuffler; +exports.some = some; +exports.sort = sort; +exports.stack = stack; +exports.stackOffsetDiverging = diverging; +exports.stackOffsetExpand = expand; +exports.stackOffsetNone = none$1; +exports.stackOffsetSilhouette = silhouette; +exports.stackOffsetWiggle = wiggle; +exports.stackOrderAppearance = appearance; +exports.stackOrderAscending = ascending; +exports.stackOrderDescending = descending; +exports.stackOrderInsideOut = insideOut; +exports.stackOrderNone = none; +exports.stackOrderReverse = reverse; +exports.stratify = stratify; +exports.style = styleValue; +exports.subset = subset; +exports.sum = sum$2; +exports.superset = superset; +exports.svg = svg; +exports.symbol = Symbol$1; +exports.symbolAsterisk = asterisk; +exports.symbolCircle = circle; +exports.symbolCross = cross; +exports.symbolDiamond = diamond; +exports.symbolDiamond2 = diamond2; +exports.symbolPlus = plus; +exports.symbolSquare = square; +exports.symbolSquare2 = square2; +exports.symbolStar = star; +exports.symbolTimes = times; +exports.symbolTriangle = triangle; +exports.symbolTriangle2 = triangle2; +exports.symbolWye = wye; +exports.symbolX = times; +exports.symbols = symbolsFill; +exports.symbolsFill = symbolsFill; +exports.symbolsStroke = symbolsStroke; +exports.text = text; +exports.thresholdFreedmanDiaconis = thresholdFreedmanDiaconis; +exports.thresholdScott = thresholdScott; +exports.thresholdSturges = thresholdSturges; +exports.tickFormat = tickFormat; +exports.tickIncrement = tickIncrement; +exports.tickStep = tickStep; +exports.ticks = ticks; +exports.timeDay = timeDay; +exports.timeDays = timeDays; +exports.timeFormatDefaultLocale = defaultLocale; +exports.timeFormatLocale = formatLocale; +exports.timeFriday = timeFriday; +exports.timeFridays = timeFridays; +exports.timeHour = timeHour; +exports.timeHours = timeHours; +exports.timeInterval = timeInterval; +exports.timeMillisecond = millisecond; +exports.timeMilliseconds = milliseconds; +exports.timeMinute = timeMinute; +exports.timeMinutes = timeMinutes; +exports.timeMonday = timeMonday; +exports.timeMondays = timeMondays; +exports.timeMonth = timeMonth; +exports.timeMonths = timeMonths; +exports.timeSaturday = timeSaturday; +exports.timeSaturdays = timeSaturdays; +exports.timeSecond = second; +exports.timeSeconds = seconds; +exports.timeSunday = timeSunday; +exports.timeSundays = timeSundays; +exports.timeThursday = timeThursday; +exports.timeThursdays = timeThursdays; +exports.timeTickInterval = timeTickInterval; +exports.timeTicks = timeTicks; +exports.timeTuesday = timeTuesday; +exports.timeTuesdays = timeTuesdays; +exports.timeWednesday = timeWednesday; +exports.timeWednesdays = timeWednesdays; +exports.timeWeek = timeSunday; +exports.timeWeeks = timeSundays; +exports.timeYear = timeYear; +exports.timeYears = timeYears; +exports.timeout = timeout; +exports.timer = timer; +exports.timerFlush = timerFlush; +exports.transition = transition; +exports.transpose = transpose; +exports.tree = tree; +exports.treemap = index; +exports.treemapBinary = binary; +exports.treemapDice = treemapDice; +exports.treemapResquarify = resquarify; +exports.treemapSlice = treemapSlice; +exports.treemapSliceDice = sliceDice; +exports.treemapSquarify = squarify; +exports.tsv = tsv; +exports.tsvFormat = tsvFormat; +exports.tsvFormatBody = tsvFormatBody; +exports.tsvFormatRow = tsvFormatRow; +exports.tsvFormatRows = tsvFormatRows; +exports.tsvFormatValue = tsvFormatValue; +exports.tsvParse = tsvParse; +exports.tsvParseRows = tsvParseRows; +exports.union = union; +exports.unixDay = unixDay; +exports.unixDays = unixDays; +exports.utcDay = utcDay; +exports.utcDays = utcDays; +exports.utcFriday = utcFriday; +exports.utcFridays = utcFridays; +exports.utcHour = utcHour; +exports.utcHours = utcHours; +exports.utcMillisecond = millisecond; +exports.utcMilliseconds = milliseconds; +exports.utcMinute = utcMinute; +exports.utcMinutes = utcMinutes; +exports.utcMonday = utcMonday; +exports.utcMondays = utcMondays; +exports.utcMonth = utcMonth; +exports.utcMonths = utcMonths; +exports.utcSaturday = utcSaturday; +exports.utcSaturdays = utcSaturdays; +exports.utcSecond = second; +exports.utcSeconds = seconds; +exports.utcSunday = utcSunday; +exports.utcSundays = utcSundays; +exports.utcThursday = utcThursday; +exports.utcThursdays = utcThursdays; +exports.utcTickInterval = utcTickInterval; +exports.utcTicks = utcTicks; +exports.utcTuesday = utcTuesday; +exports.utcTuesdays = utcTuesdays; +exports.utcWednesday = utcWednesday; +exports.utcWednesdays = utcWednesdays; +exports.utcWeek = utcSunday; +exports.utcWeeks = utcSundays; +exports.utcYear = utcYear; +exports.utcYears = utcYears; +exports.variance = variance; +exports.version = version; +exports.window = defaultView; +exports.xml = xml; +exports.zip = zip; +exports.zoom = zoom; +exports.zoomIdentity = identity; +exports.zoomTransform = transform; + +})); diff --git a/snag/static/pagedown.css b/snag/static/pagedown.css index 7f9c3e0..633f8c1 100644 --- a/snag/static/pagedown.css +++ b/snag/static/pagedown.css @@ -153,3 +153,10 @@ footer { width: 50%; margin: 0 0 1rem } + +div.graph { + max-width: 80%; + box-shadow: 1px 1px 5px 0 rgba(0,0,0,.4); + margin: 0 auto +} + diff --git a/snag/static/plot.js b/snag/static/plot.js new file mode 100644 index 0000000..5964a4d --- /dev/null +++ b/snag/static/plot.js @@ -0,0 +1,14264 @@ +// @observablehq/plot v0.6.11 Copyright 2020-2023 Observable, Inc. +(function (global, factory) { +typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3@7.8.5/dist/d3.min.js')) : +typeof define === 'function' && define.amd ? define(['exports', 'd3@7.8.5/dist/d3.min.js'], factory) : +(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Plot = global.Plot || {}, global.d3)); +})(this, (function (exports, d3) { 'use strict'; + +var version = "0.6.11"; + +function defined(x) { + return x != null && !Number.isNaN(x); +} + +function ascendingDefined(a, b) { + return +defined(b) - +defined(a) || d3.ascending(a, b); +} + +function descendingDefined(a, b) { + return +defined(b) - +defined(a) || d3.descending(a, b); +} + +function nonempty(x) { + return x != null && `${x}` !== ""; +} + +function finite$1(x) { + return isFinite(x) ? x : NaN; +} + +function positive(x) { + return x > 0 && isFinite(x) ? x : NaN; +} + +function negative(x) { + return x < 0 && isFinite(x) ? x : NaN; +} + +function format(date, fallback) { + if (!(date instanceof Date)) date = new Date(+date); + if (isNaN(date)) return typeof fallback === "function" ? fallback(date) : fallback; + const hours = date.getUTCHours(); + const minutes = date.getUTCMinutes(); + const seconds = date.getUTCSeconds(); + const milliseconds = date.getUTCMilliseconds(); + return `${formatYear(date.getUTCFullYear())}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}${ + hours || minutes || seconds || milliseconds ? `T${pad(hours, 2)}:${pad(minutes, 2)}${ + seconds || milliseconds ? `:${pad(seconds, 2)}${ + milliseconds ? `.${pad(milliseconds, 3)}` : `` + }` : `` + }Z` : `` + }`; +} + +function formatYear(year) { + return year < 0 ? `-${pad(-year, 6)}` + : year > 9999 ? `+${pad(year, 6)}` + : pad(year, 4); +} + +function pad(value, width) { + return `${value}`.padStart(width, "0"); +} + +const re = /^(?:[-+]\d{2})?\d{4}(?:-\d{2}(?:-\d{2})?)?(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d{3})?)?(?:Z|[-+]\d{2}:?\d{2})?)?$/; + +function parse(string, fallback) { + if (!re.test(string += "")) return typeof fallback === "function" ? fallback(string) : fallback; + return new Date(string); +} + +const durationSecond = 1000; +const durationMinute = durationSecond * 60; +const durationHour = durationMinute * 60; +const durationDay = durationHour * 24; +const durationWeek = durationDay * 7; +const durationMonth = durationDay * 30; +const durationYear = durationDay * 365; + +// See https://github.com/d3/d3-time/blob/9e8dc940f38f78d7588aad68a54a25b1f0c2d97b/src/ticks.js#L14-L33 +const tickIntervals = [ + ["millisecond", 1], + ["2 milliseconds", 2], + ["5 milliseconds", 5], + ["10 milliseconds", 10], + ["20 milliseconds", 20], + ["50 milliseconds", 50], + ["100 milliseconds", 100], + ["200 milliseconds", 200], + ["500 milliseconds", 500], + ["second", durationSecond], + ["5 seconds", 5 * durationSecond], + ["15 seconds", 15 * durationSecond], + ["30 seconds", 30 * durationSecond], + ["minute", durationMinute], + ["5 minutes", 5 * durationMinute], + ["15 minutes", 15 * durationMinute], + ["30 minutes", 30 * durationMinute], + ["hour", durationHour], + ["3 hours", 3 * durationHour], + ["6 hours", 6 * durationHour], + ["12 hours", 12 * durationHour], + ["day", durationDay], + ["2 days", 2 * durationDay], + ["week", durationWeek], + ["2 weeks", 2 * durationWeek], // https://github.com/d3/d3-time/issues/46 + ["month", durationMonth], + ["3 months", 3 * durationMonth], + ["6 months", 6 * durationMonth], // https://github.com/d3/d3-time/issues/46 + ["year", durationYear], + ["2 years", 2 * durationYear], + ["5 years", 5 * durationYear], + ["10 years", 10 * durationYear], + ["20 years", 20 * durationYear], + ["50 years", 50 * durationYear], + ["100 years", 100 * durationYear] // TODO generalize to longer time scales +]; + +const durations = new Map([ + ["second", durationSecond], + ["minute", durationMinute], + ["hour", durationHour], + ["day", durationDay], + ["monday", durationWeek], + ["tuesday", durationWeek], + ["wednesday", durationWeek], + ["thursday", durationWeek], + ["friday", durationWeek], + ["saturday", durationWeek], + ["sunday", durationWeek], + ["week", durationWeek], + ["month", durationMonth], + ["year", durationYear] +]); + +const timeIntervals = new Map([ + ["second", d3.timeSecond], + ["minute", d3.timeMinute], + ["hour", d3.timeHour], + ["day", d3.timeDay], // https://github.com/d3/d3-time/issues/62 + ["monday", d3.timeMonday], + ["tuesday", d3.timeTuesday], + ["wednesday", d3.timeWednesday], + ["thursday", d3.timeThursday], + ["friday", d3.timeFriday], + ["saturday", d3.timeSaturday], + ["sunday", d3.timeSunday], + ["week", d3.timeWeek], + ["month", d3.timeMonth], + ["year", d3.timeYear] +]); + +const utcIntervals = new Map([ + ["second", d3.utcSecond], + ["minute", d3.utcMinute], + ["hour", d3.utcHour], + ["day", d3.unixDay], + ["monday", d3.utcMonday], + ["tuesday", d3.utcTuesday], + ["wednesday", d3.utcWednesday], + ["thursday", d3.utcThursday], + ["friday", d3.utcFriday], + ["saturday", d3.utcSaturday], + ["sunday", d3.utcSunday], + ["week", d3.utcWeek], + ["month", d3.utcMonth], + ["year", d3.utcYear] +]); + +// These hidden fields describe standard intervals so that we can, for example, +// generalize a scale’s time interval to a larger ticks time interval to reduce +// the number of displayed ticks. TODO We could instead allow the interval +// implementation to expose a “generalize” method that returns a larger, aligned +// interval; that would allow us to move this logic to D3, and allow +// generalization even when a custom interval is provided. +const intervalDuration = Symbol("intervalDuration"); +const intervalType = Symbol("intervalType"); + +// We greedily mutate D3’s standard intervals on load so that the hidden fields +// are available even if specified as e.g. d3.utcMonth instead of "month". +for (const [name, interval] of timeIntervals) { + interval[intervalDuration] = durations.get(name); + interval[intervalType] = "time"; +} +for (const [name, interval] of utcIntervals) { + interval[intervalDuration] = durations.get(name); + interval[intervalType] = "utc"; +} + +const utcFormatIntervals = [ + ["year", d3.utcYear, "utc"], + ["month", d3.utcMonth, "utc"], + ["day", d3.unixDay, "utc", 6 * durationMonth], + ["hour", d3.utcHour, "utc", 3 * durationDay], + ["minute", d3.utcMinute, "utc", 6 * durationHour], + ["second", d3.utcSecond, "utc", 30 * durationMinute] +]; + +const timeFormatIntervals = [ + ["year", d3.timeYear, "time"], + ["month", d3.timeMonth, "time"], + ["day", d3.timeDay, "time", 6 * durationMonth], + ["hour", d3.timeHour, "time", 3 * durationDay], + ["minute", d3.timeMinute, "time", 6 * durationHour], + ["second", d3.timeSecond, "time", 30 * durationMinute] +]; + +// An interleaved array of UTC and local time intervals, in descending order +// from largest to smallest, used to determine the most specific standard time +// format for a given array of dates. This is a subset of the tick intervals +// listed above; we only need the breakpoints where the format changes. +const formatIntervals = [ + utcFormatIntervals[0], + timeFormatIntervals[0], + utcFormatIntervals[1], + timeFormatIntervals[1], + utcFormatIntervals[2], + timeFormatIntervals[2], + // Below day, local time typically has an hourly offset from UTC and hence the + // two are aligned and indistinguishable; therefore, we only consider UTC, and + // we don’t consider these if the domain only has a single value. + ...utcFormatIntervals.slice(3) +]; + +function parseTimeInterval(input) { + let name = `${input}`.toLowerCase(); + if (name.endsWith("s")) name = name.slice(0, -1); // drop plural + let period = 1; + const match = /^(?:(\d+)\s+)/.exec(name); + if (match) { + name = name.slice(match[0].length); + period = +match[1]; + } + switch (name) { + case "quarter": + name = "month"; + period *= 3; + break; + case "half": + name = "month"; + period *= 6; + break; + } + let interval = utcIntervals.get(name); + if (!interval) throw new Error(`unknown interval: ${input}`); + if (period > 1 && !interval.every) throw new Error(`non-periodic interval: ${name}`); + return [name, period]; +} + +function maybeTimeInterval(input) { + return asInterval(parseTimeInterval(input), "time"); +} + +function maybeUtcInterval(input) { + return asInterval(parseTimeInterval(input), "utc"); +} + +function asInterval([name, period], type) { + let interval = (type === "time" ? timeIntervals : utcIntervals).get(name); + if (period > 1) { + interval = interval.every(period); + interval[intervalDuration] = durations.get(name) * period; + interval[intervalType] = type; + } + return interval; +} + +// If the given interval is a standard time interval, we may be able to promote +// it a larger aligned time interval, rather than showing every nth tick. +function generalizeTimeInterval(interval, n) { + if (!(n > 1)) return; // no need to generalize + const duration = interval[intervalDuration]; + if (!tickIntervals.some(([, d]) => d === duration)) return; // nonstandard or unknown interval + if (duration % durationDay === 0 && durationDay < duration && duration < durationMonth) return; // not generalizable + const [i] = tickIntervals[d3.bisector(([, step]) => Math.log(step)).center(tickIntervals, Math.log(duration * n))]; + return (interval[intervalType] === "time" ? maybeTimeInterval : maybeUtcInterval)(i); +} + +function formatTimeInterval(name, type, anchor) { + const format = type === "time" ? d3.timeFormat : d3.utcFormat; + // For tips and legends, use a format that doesn’t require context. + if (anchor == null) { + return format( + name === "year" + ? "%Y" + : name === "month" + ? "%Y-%m" + : name === "day" + ? "%Y-%m-%d" + : name === "hour" || name === "minute" + ? "%Y-%m-%dT%H:%M" + : name === "second" + ? "%Y-%m-%dT%H:%M:%S" + : "%Y-%m-%dT%H:%M:%S.%L" + ); + } + // Otherwise, assume that this is for axis ticks. + const template = getTimeTemplate(anchor); + switch (name) { + case "millisecond": + return formatConditional(format(".%L"), format(":%M:%S"), template); + case "second": + return formatConditional(format(":%S"), format("%-I:%M"), template); + case "minute": + return formatConditional(format("%-I:%M"), format("%p"), template); + case "hour": + return formatConditional(format("%-I %p"), format("%b %-d"), template); + case "day": + return formatConditional(format("%-d"), format("%b"), template); + case "month": + return formatConditional(format("%b"), format("%Y"), template); + case "year": + return format("%Y"); + } + throw new Error("unable to format time ticks"); +} + +function getTimeTemplate(anchor) { + return anchor === "left" || anchor === "right" + ? (f1, f2) => `\n${f1}\n${f2}` // extra newline to keep f1 centered + : anchor === "top" + ? (f1, f2) => `${f2}\n${f1}` + : (f1, f2) => `${f1}\n${f2}`; +} + +function getFormatIntervals(type) { + return type === "time" ? timeFormatIntervals : type === "utc" ? utcFormatIntervals : formatIntervals; +} + +// Given an array of dates, returns the largest compatible standard time +// interval. If no standard interval is compatible (other than milliseconds, +// which is universally compatible), returns undefined. +function inferTimeFormat(type, dates, anchor) { + const step = d3.max(d3.pairs(dates, (a, b) => Math.abs(b - a))); // maybe undefined! + if (step < 1000) return formatTimeInterval("millisecond", "utc", anchor); + for (const [name, interval, intervalType, maxStep] of getFormatIntervals(type)) { + if (step > maxStep) break; // e.g., 52 weeks + if (name === "hour" && !step) break; // e.g., domain with a single date + if (dates.every((d) => interval.floor(d) >= d)) return formatTimeInterval(name, intervalType, anchor); + } +} + +function formatConditional(format1, format2, template) { + return (x, i, X) => { + const f1 = format1(x, i); // always shown + const f2 = format2(x, i); // only shown if different + const j = i - orderof(X); // detect reversed domains + return i !== j && X[j] !== undefined && f2 === format2(X[j], j) ? f1 : template(f1, f2); + }; +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray +const TypedArray = Object.getPrototypeOf(Uint8Array); +const objectToString = Object.prototype.toString; + +// If a reindex is attached to the data, channel values expressed as arrays will +// be reindexed when the channels are instantiated. See exclusiveFacets. +const reindex = Symbol("reindex"); + +function valueof(data, value, type) { + const valueType = typeof value; + return valueType === "string" + ? maybeTypedMap(data, field(value), type) + : valueType === "function" + ? maybeTypedMap(data, value, type) + : valueType === "number" || value instanceof Date || valueType === "boolean" + ? map$1(data, constant(value), type) + : typeof value?.transform === "function" + ? maybeTypedArrayify(value.transform(data), type) + : maybeTake(maybeTypedArrayify(value, type), data?.[reindex]); +} + +function maybeTake(values, index) { + return index ? take(values, index) : values; +} + +function maybeTypedMap(data, f, type) { + return map$1(data, type?.prototype instanceof TypedArray ? floater(f) : f, type); +} + +function maybeTypedArrayify(data, type) { + return type === undefined + ? arrayify(data) // preserve undefined type + : data instanceof type + ? data + : type.prototype instanceof TypedArray && !(data instanceof TypedArray) + ? type.from(data, coerceNumber) + : type.from(data); +} + +function floater(f) { + return (d, i) => coerceNumber(f(d, i)); +} + +const singleton = [null]; // for data-less decoration marks, e.g. frame +const field = (name) => (d) => d[name]; +const indexOf = {transform: range}; +const identity$1 = {transform: (d) => d}; +const one = () => 1; +const yes = () => true; +const string = (x) => (x == null ? x : `${x}`); +const number$1 = (x) => (x == null ? x : +x); +const first = (x) => (x ? x[0] : undefined); +const second = (x) => (x ? x[1] : undefined); +const third = (x) => (x ? x[2] : undefined); +const constant = (x) => () => x; + +// Converts a string like “p25” into a function that takes an index I and an +// accessor function f, returning the corresponding percentile value. +function percentile(reduce) { + const p = +`${reduce}`.slice(1) / 100; + return (I, f) => d3.quantile(I, p, f); +} + +// If the values are specified as a typed array, no coercion is required. +function coerceNumbers(values) { + return values instanceof TypedArray ? values : map$1(values, coerceNumber, Float64Array); +} + +// Unlike Mark’s number, here we want to convert null and undefined to NaN since +// the result will be stored in a Float64Array and we don’t want null to be +// coerced to zero. We use Number instead of unary + to allow BigInt coercion. +function coerceNumber(x) { + return x == null ? NaN : Number(x); +} + +function coerceDates(values) { + return map$1(values, coerceDate); +} + +// When coercing strings to dates, we only want to allow the ISO 8601 format +// since the built-in string parsing of the Date constructor varies across +// browsers. (In the future, this could be made more liberal if desired, though +// it is still generally preferable to do date parsing yourself explicitly, +// rather than rely on Plot.) Any non-string values are coerced to number first +// and treated as milliseconds since UNIX epoch. +function coerceDate(x) { + return x instanceof Date && !isNaN(x) + ? x + : typeof x === "string" + ? parse(x) + : x == null || isNaN((x = +x)) + ? undefined + : new Date(x); +} + +// Some channels may allow a string constant to be specified; to differentiate +// string constants (e.g., "red") from named fields (e.g., "date"), this +// function tests whether the given value is a CSS color string and returns a +// tuple [channel, constant] where one of the two is undefined, and the other is +// the given value. If you wish to reference a named field that is also a valid +// CSS color, use an accessor (d => d.red) instead. +function maybeColorChannel(value, defaultValue) { + if (value === undefined) value = defaultValue; + return value === null ? [undefined, "none"] : isColor(value) ? [undefined, value] : [value, undefined]; +} + +// Similar to maybeColorChannel, this tests whether the given value is a number +// indicating a constant, and otherwise assumes that it’s a channel value. +function maybeNumberChannel(value, defaultValue) { + if (value === undefined) value = defaultValue; + return value === null || typeof value === "number" ? [undefined, value] : [value, undefined]; +} + +// Validates the specified optional string against the allowed list of keywords. +function maybeKeyword(input, name, allowed) { + if (input != null) return keyword(input, name, allowed); +} + +// Validates the specified required string against the allowed list of keywords. +function keyword(input, name, allowed) { + const i = `${input}`.toLowerCase(); + if (!allowed.includes(i)) throw new Error(`invalid ${name}: ${input}`); + return i; +} + +// Promotes the specified data to an array as needed. +function arrayify(data) { + return data == null || data instanceof Array || data instanceof TypedArray ? data : Array.from(data); +} + +// An optimization of type.from(values, f): if the given values are already an +// instanceof the desired array type, the faster values.map method is used. +function map$1(values, f, type = Array) { + return values == null ? values : values instanceof type ? values.map(f) : type.from(values, f); +} + +// An optimization of type.from(values): if the given values are already an +// instanceof the desired array type, the faster values.slice method is used. +function slice(values, type = Array) { + return values instanceof type ? values.slice() : type.from(values); +} + +// Returns true if any of x, x1, or x2 is not (strictly) undefined. +function hasX({x, x1, x2}) { + return x !== undefined || x1 !== undefined || x2 !== undefined; +} + +// Returns true if any of y, y1, or y2 is not (strictly) undefined. +function hasY({y, y1, y2}) { + return y !== undefined || y1 !== undefined || y2 !== undefined; +} + +// Returns true if has x or y, or if interval is not (strictly) undefined. +function hasXY(options) { + return hasX(options) || hasY(options) || options.interval !== undefined; +} + +// Disambiguates an options object (e.g., {y: "x2"}) from a primitive value. +function isObject(option) { + return option?.toString === objectToString; +} + +// Disambiguates a scale options object (e.g., {color: {type: "linear"}}) from +// some other option (e.g., {color: "red"}). When creating standalone legends, +// this is used to test whether a scale is defined; this should be consistent +// with inferScaleType when there are no channels associated with the scale, and +// if this returns true, then normalizeScale must return non-null. +function isScaleOptions(option) { + return isObject(option) && (option.type !== undefined || option.domain !== undefined); +} + +// Disambiguates an options object (e.g., {y: "x2"}) from a channel value +// definition expressed as a channel transform (e.g., {transform: …}). +// TODO Check typeof option[Symbol.iterator] !== "function"? +function isOptions(option) { + return isObject(option) && typeof option.transform !== "function"; +} + +// Disambiguates a sort transform (e.g., {sort: "date"}) from a channel domain +// sort definition (e.g., {sort: {y: "x"}}). +function isDomainSort(sort) { + return isOptions(sort) && sort.value === undefined && sort.channel === undefined; +} + +// For marks specified either as [0, x] or [x1, x2], such as areas and bars. +function maybeZero(x, x1, x2, x3 = identity$1) { + if (x1 === undefined && x2 === undefined) { + // {x} or {} + (x1 = 0), (x2 = x === undefined ? x3 : x); + } else if (x1 === undefined) { + // {x, x2} or {x2} + x1 = x === undefined ? 0 : x; + } else if (x2 === undefined) { + // {x, x1} or {x1} + x2 = x === undefined ? 0 : x; + } + return [x1, x2]; +} + +// For marks that have x and y channels (e.g., cell, dot, line, text). +function maybeTuple(x, y) { + return x === undefined && y === undefined ? [first, second] : [x, y]; +} + +// A helper for extracting the z channel, if it is variable. Used by transforms +// that require series, such as moving average and normalize. +function maybeZ({z, fill, stroke} = {}) { + if (z === undefined) [z] = maybeColorChannel(fill); + if (z === undefined) [z] = maybeColorChannel(stroke); + return z; +} + +// Returns a Uint32Array with elements [0, 1, 2, … data.length - 1]. +function range(data) { + const n = data.length; + const r = new Uint32Array(n); + for (let i = 0; i < n; ++i) r[i] = i; + return r; +} + +// Returns an array [values[index[0]], values[index[1]], …]. +function take(values, index) { + return map$1(index, (i) => values[i], values.constructor); +} + +// If f does not take exactly one argument, wraps it in a function that uses take. +function taker(f) { + return f.length === 1 ? (index, values) => f(take(values, index)) : f; +} + +// Uses subarray if available, and otherwise slice. +function subarray(I, i, j) { + return I.subarray ? I.subarray(i, j) : I.slice(i, j); +} + +// Based on InternMap (d3.group). +function keyof(value) { + return value !== null && typeof value === "object" ? value.valueOf() : value; +} + +function maybeInput(key, options) { + if (options[key] !== undefined) return options[key]; + switch (key) { + case "x1": + case "x2": + key = "x"; + break; + case "y1": + case "y2": + key = "y"; + break; + } + return options[key]; +} + +function column(source) { + // Defines a column whose values are lazily populated by calling the returned + // setter. If the given source is labeled, the label is propagated to the + // returned column definition. + let value; + return [ + { + transform: () => value, + label: labelof(source) + }, + (v) => (value = v) + ]; +} + +// Like column, but allows the source to be null. +function maybeColumn(source) { + return source == null ? [source] : column(source); +} + +function labelof(value, defaultValue) { + return typeof value === "string" ? value : value && value.label !== undefined ? value.label : defaultValue; +} + +// Assuming that both x1 and x2 and lazy columns (per above), this derives a new +// a column that’s the average of the two, and which inherits the column label +// (if any). Both input columns are assumed to be quantitative. If either column +// is temporal, the returned column is also temporal. +function mid(x1, x2) { + return { + transform(data) { + const X1 = x1.transform(data); + const X2 = x2.transform(data); + return isTemporal(X1) || isTemporal(X2) + ? map$1(X1, (_, i) => new Date((+X1[i] + +X2[i]) / 2)) + : map$1(X1, (_, i) => (+X1[i] + +X2[i]) / 2, Float64Array); + }, + label: x1.label + }; +} + +// If the scale options declare an interval, applies it to the values V. +function maybeApplyInterval(V, scale) { + const t = maybeIntervalTransform(scale?.interval, scale?.type); + return t ? map$1(V, t) : V; +} + +// Returns the equivalent scale transform for the specified interval option. +function maybeIntervalTransform(interval, type) { + const i = maybeInterval(interval, type); + return i && ((v) => (defined(v) ? i.floor(v) : v)); +} + +// If interval is not nullish, converts interval shorthand such as a number (for +// multiples) or a time interval name (such as “day”) to a {floor, offset, +// range} object similar to a D3 time interval. +function maybeInterval(interval, type) { + if (interval == null) return; + if (typeof interval === "number") { + if (0 < interval && interval < 1 && Number.isInteger(1 / interval)) interval = -1 / interval; + const n = Math.abs(interval); + return interval < 0 + ? { + floor: (d) => Math.floor(d * n) / n, + offset: (d) => (d * n + 1) / n, // note: no optional step for simplicity + range: (lo, hi) => d3.range(Math.ceil(lo * n), hi * n).map((x) => x / n) + } + : { + floor: (d) => Math.floor(d / n) * n, + offset: (d) => d + n, // note: no optional step for simplicity + range: (lo, hi) => d3.range(Math.ceil(lo / n), hi / n).map((x) => x * n) + }; + } + if (typeof interval === "string") return (type === "time" ? maybeTimeInterval : maybeUtcInterval)(interval); + if (typeof interval.floor !== "function") throw new Error("invalid interval; missing floor method"); + if (typeof interval.offset !== "function") throw new Error("invalid interval; missing offset method"); + return interval; +} + +// Like maybeInterval, but requires a range method too. +function maybeRangeInterval(interval, type) { + interval = maybeInterval(interval, type); + if (interval && typeof interval.range !== "function") throw new Error("invalid interval: missing range method"); + return interval; +} + +// Like maybeRangeInterval, but requires a ceil method too. +function maybeNiceInterval(interval, type) { + interval = maybeRangeInterval(interval, type); + if (interval && typeof interval.ceil !== "function") throw new Error("invalid interval: missing ceil method"); + return interval; +} + +function isTimeInterval(t) { + return isInterval(t) && typeof t?.floor === "function" && t.floor() instanceof Date; +} + +function isInterval(t) { + return typeof t?.range === "function"; +} + +// This distinguishes between per-dimension options and a standalone value. +function maybeValue(value) { + return value === undefined || isOptions(value) ? value : {value}; +} + +// Coerces the given channel values (if any) to numbers. This is useful when +// values will be interpolated into other code, such as an SVG transform, and +// where we don’t wish to allow unexpected behavior for weird input. +function numberChannel(source) { + return source == null + ? null + : { + transform: (data) => valueof(data, source, Float64Array), + label: labelof(source) + }; +} + +function isTuples(data) { + if (!isIterable(data)) return false; + for (const d of data) { + if (d == null) continue; + return typeof d === "object" && "0" in d && "1" in d; + } +} + +function isIterable(value) { + return value && typeof value[Symbol.iterator] === "function"; +} + +function isTextual(values) { + for (const value of values) { + if (value == null) continue; + return typeof value !== "object" || value instanceof Date; + } +} + +function isOrdinal(values) { + for (const value of values) { + if (value == null) continue; + const type = typeof value; + return type === "string" || type === "boolean"; + } +} + +function isTemporal(values) { + for (const value of values) { + if (value == null) continue; + return value instanceof Date; + } +} + +// Are these strings that might represent dates? This is stricter than ISO 8601 +// because we want to ignore false positives on numbers; for example, the string +// "1192" is more likely to represent a number than a date even though it is +// valid ISO 8601 representing 1192-01-01. +function isTemporalString(values) { + for (const value of values) { + if (value == null) continue; + return typeof value === "string" && isNaN(value) && parse(value); + } +} + +// Are these strings that might represent numbers? This is stricter than +// coercion because we want to ignore false positives on e.g. empty strings. +function isNumericString(values) { + for (const value of values) { + if (value == null) continue; + if (typeof value !== "string") return false; + if (!value.trim()) continue; + return !isNaN(value); + } +} + +function isNumeric(values) { + for (const value of values) { + if (value == null) continue; + return typeof value === "number"; + } +} + +// Returns true if every non-null value in the specified iterable of values +// passes the specified predicate, and there is at least one non-null value; +// returns false if at least one non-null value does not pass the specified +// predicate; otherwise returns undefined (as if all values are null). +function isEvery(values, is) { + let every; + for (const value of values) { + if (value == null) continue; + if (!is(value)) return false; + every = true; + } + return every; +} + +const namedColors = new Set("none,currentcolor,transparent,aliceblue,antiquewhite,aqua,aquamarine,azure,beige,bisque,black,blanchedalmond,blue,blueviolet,brown,burlywood,cadetblue,chartreuse,chocolate,coral,cornflowerblue,cornsilk,crimson,cyan,darkblue,darkcyan,darkgoldenrod,darkgray,darkgreen,darkgrey,darkkhaki,darkmagenta,darkolivegreen,darkorange,darkorchid,darkred,darksalmon,darkseagreen,darkslateblue,darkslategray,darkslategrey,darkturquoise,darkviolet,deeppink,deepskyblue,dimgray,dimgrey,dodgerblue,firebrick,floralwhite,forestgreen,fuchsia,gainsboro,ghostwhite,gold,goldenrod,gray,green,greenyellow,grey,honeydew,hotpink,indianred,indigo,ivory,khaki,lavender,lavenderblush,lawngreen,lemonchiffon,lightblue,lightcoral,lightcyan,lightgoldenrodyellow,lightgray,lightgreen,lightgrey,lightpink,lightsalmon,lightseagreen,lightskyblue,lightslategray,lightslategrey,lightsteelblue,lightyellow,lime,limegreen,linen,magenta,maroon,mediumaquamarine,mediumblue,mediumorchid,mediumpurple,mediumseagreen,mediumslateblue,mediumspringgreen,mediumturquoise,mediumvioletred,midnightblue,mintcream,mistyrose,moccasin,navajowhite,navy,oldlace,olive,olivedrab,orange,orangered,orchid,palegoldenrod,palegreen,paleturquoise,palevioletred,papayawhip,peachpuff,peru,pink,plum,powderblue,purple,rebeccapurple,red,rosybrown,royalblue,saddlebrown,salmon,sandybrown,seagreen,seashell,sienna,silver,skyblue,slateblue,slategray,slategrey,snow,springgreen,steelblue,tan,teal,thistle,tomato,turquoise,violet,wheat,white,whitesmoke,yellow".split(",")); // prettier-ignore + +// Returns true if value is a valid CSS color string. This is intentionally lax +// because the CSS color spec keeps growing, and we don’t need to parse these +// colors—we just need to disambiguate them from column names. +// https://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint +// https://www.w3.org/TR/css-color-5/ +function isColor(value) { + if (typeof value !== "string") return false; + value = value.toLowerCase().trim(); + return ( + /^#[0-9a-f]{3,8}$/.test(value) || // hex rgb, rgba, rrggbb, rrggbbaa + /^(?:url|var|rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color|color-mix)\(.*\)$/.test(value) || // <funciri>, CSS variable, color, etc. + namedColors.has(value) // currentColor, red, etc. + ); +} + +function isOpacity(value) { + return typeof value === "number" && ((0 <= value && value <= 1) || isNaN(value)); +} + +function isNoneish(value) { + return value == null || isNone(value); +} + +function isNone(value) { + return /^\s*none\s*$/i.test(value); +} + +function isRound(value) { + return /^\s*round\s*$/i.test(value); +} + +function maybeAnchor$3(value, name) { + return maybeKeyword(value, name, [ + "middle", + "top-left", + "top", + "top-right", + "right", + "bottom-right", + "bottom", + "bottom-left", + "left" + ]); +} + +function maybeFrameAnchor(value = "middle") { + return maybeAnchor$3(value, "frameAnchor"); +} + +// Like a sort comparator, returns a positive value if the given array of values +// is in ascending order, a negative value if the values are in descending +// order. Assumes monotonicity; only tests the first and last values. +function orderof(values) { + if (values == null) return; + const first = values[0]; + const last = values[values.length - 1]; + return d3.descending(first, last); +} + +// Unlike {...defaults, ...options}, this ensures that any undefined (but +// present) properties in options inherit the given default value. +function inherit(options = {}, ...rest) { + let o = options; + for (const defaults of rest) { + for (const key in defaults) { + if (o[key] === undefined) { + const value = defaults[key]; + if (o === options) o = {...o, [key]: value}; + else o[key] = value; + } + } + } + return o; +} + +// Given an iterable of named things (objects with a name property), returns a +// corresponding object with properties associated with the given name. +function named(things) { + console.warn("named iterables are deprecated; please use an object instead"); + const names = new Set(); + return Object.fromEntries( + Array.from(things, (thing) => { + const {name} = thing; + if (name == null) throw new Error("missing name"); + const key = `${name}`; + if (key === "__proto__") throw new Error(`illegal name: ${key}`); + if (names.has(key)) throw new Error(`duplicate name: ${key}`); + names.add(key); + return [name, thing]; + }) + ); +} + +function maybeNamed(things) { + return isIterable(things) ? named(things) : things; +} + +// Positional scales have associated axes, and for ordinal data, a point or band +// scale is used instead of an ordinal scale. +const position$1 = Symbol("position"); + +// Color scales default to the turbo interpolator for quantitative data, and to +// the Tableau10 scheme for ordinal data. Color scales may also have an +// associated legend. +const color = Symbol("color"); + +// Radius scales default to the sqrt type, have a default range of [0, 3], and a +// default domain from 0 to the median first quartile of associated channels. +const radius = Symbol("radius"); + +// Length scales default to the linear type, have a default range of [0, 12], +// and a default domain from 0 to the median median of associated channels. +const length = Symbol("length"); + +// Opacity scales have a default range of [0, 1], and a default domain from 0 to +// the maximum value of associated channels. +const opacity = Symbol("opacity"); + +// Symbol scales have a default range of categorical symbols. +const symbol = Symbol("symbol"); + +// There isn’t really a projection scale; this represents x and y for geometry. +// This is used to denote channels that should be projected. +const projection = Symbol("projection"); + +// TODO Rather than hard-coding the list of known scale names, collect the names +// and categories for each plot specification, so that custom marks can register +// custom scales. +const registry = new Map([ + ["x", position$1], + ["y", position$1], + ["fx", position$1], + ["fy", position$1], + ["r", radius], + ["color", color], + ["opacity", opacity], + ["symbol", symbol], + ["length", length], + ["projection", projection] +]); + +function isPosition(kind) { + return kind === position$1 || kind === projection; +} + +function hasNumericRange(kind) { + return kind === position$1 || kind === radius || kind === length || kind === opacity; +} + +const sqrt3 = Math.sqrt(3); +const sqrt4_3 = 2 / sqrt3; + +const symbolHexagon = { + draw(context, size) { + const rx = Math.sqrt(size / Math.PI), + ry = rx * sqrt4_3, + hy = ry / 2; + context.moveTo(0, ry); + context.lineTo(rx, hy); + context.lineTo(rx, -hy); + context.lineTo(0, -ry); + context.lineTo(-rx, -hy); + context.lineTo(-rx, hy); + context.closePath(); + } +}; + +const symbols = new Map([ + ["asterisk", d3.symbolAsterisk], + ["circle", d3.symbolCircle], + ["cross", d3.symbolCross], + ["diamond", d3.symbolDiamond], + ["diamond2", d3.symbolDiamond2], + ["hexagon", symbolHexagon], + ["plus", d3.symbolPlus], + ["square", d3.symbolSquare], + ["square2", d3.symbolSquare2], + ["star", d3.symbolStar], + ["times", d3.symbolTimes], + ["triangle", d3.symbolTriangle], + ["triangle2", d3.symbolTriangle2], + ["wye", d3.symbolWye] +]); + +function isSymbolObject(value) { + return value && typeof value.draw === "function"; +} + +function isSymbol(value) { + if (isSymbolObject(value)) return true; + if (typeof value !== "string") return false; + return symbols.has(value.toLowerCase()); +} + +function maybeSymbol(symbol) { + if (symbol == null || isSymbolObject(symbol)) return symbol; + const value = symbols.get(`${symbol}`.toLowerCase()); + if (value) return value; + throw new Error(`invalid symbol: ${symbol}`); +} + +function maybeSymbolChannel(symbol) { + if (symbol == null || isSymbolObject(symbol)) return [undefined, symbol]; + if (typeof symbol === "string") { + const value = symbols.get(`${symbol}`.toLowerCase()); + if (value) return [undefined, value]; + } + return [symbol, undefined]; +} + +function basic({filter: f1, sort: s1, reverse: r1, transform: t1, initializer: i1, ...options} = {}, transform) { + // If both t1 and t2 are defined, returns a composite transform that first + // applies t1 and then applies t2. + if (t1 === undefined) { + // explicit transform overrides filter, sort, and reverse + if (f1 != null) t1 = filterTransform(f1); + if (s1 != null && !isDomainSort(s1)) t1 = composeTransform(t1, sortTransform(s1)); + if (r1) t1 = composeTransform(t1, reverseTransform); + } + if (transform != null && i1 != null) throw new Error("transforms cannot be applied after initializers"); + return { + ...options, + ...((s1 === null || isDomainSort(s1)) && {sort: s1}), + transform: composeTransform(t1, transform) + }; +} + +function initializer({filter: f1, sort: s1, reverse: r1, initializer: i1, ...options} = {}, initializer) { + // If both i1 and i2 are defined, returns a composite initializer that first + // applies i1 and then applies i2. + if (i1 === undefined) { + // explicit initializer overrides filter, sort, and reverse + if (f1 != null) i1 = filterTransform(f1); + if (s1 != null && !isDomainSort(s1)) i1 = composeInitializer(i1, sortTransform(s1)); + if (r1) i1 = composeInitializer(i1, reverseTransform); + } + return { + ...options, + ...((s1 === null || isDomainSort(s1)) && {sort: s1}), + initializer: composeInitializer(i1, initializer) + }; +} + +function composeTransform(t1, t2) { + if (t1 == null) return t2 === null ? undefined : t2; + if (t2 == null) return t1 === null ? undefined : t1; + return function (data, facets, plotOptions) { + ({data, facets} = t1.call(this, data, facets, plotOptions)); + return t2.call(this, arrayify(data), facets, plotOptions); + }; +} + +function composeInitializer(i1, i2) { + if (i1 == null) return i2 === null ? undefined : i2; + if (i2 == null) return i1 === null ? undefined : i1; + return function (data, facets, channels, ...args) { + let c1, d1, f1, c2, d2, f2; + ({data: d1 = data, facets: f1 = facets, channels: c1} = i1.call(this, data, facets, channels, ...args)); + ({data: d2 = d1, facets: f2 = f1, channels: c2} = i2.call(this, d1, f1, {...channels, ...c1}, ...args)); + return {data: d2, facets: f2, channels: {...c1, ...c2}}; + }; +} + +function apply(options, t) { + return (options.initializer != null ? initializer : basic)(options, t); +} + +function filter(test, options) { + return apply(options, filterTransform(test)); +} + +function filterTransform(value) { + return (data, facets) => { + const V = valueof(data, value); + return {data, facets: facets.map((I) => I.filter((i) => V[i]))}; + }; +} + +function reverse({sort, ...options} = {}) { + return { + ...apply(options, reverseTransform), + sort: isDomainSort(sort) ? sort : null + }; +} + +function reverseTransform(data, facets) { + return {data, facets: facets.map((I) => I.slice().reverse())}; +} + +function shuffle({seed, sort, ...options} = {}) { + return { + ...apply(options, sortValue(seed == null ? Math.random : d3.randomLcg(seed))), + sort: isDomainSort(sort) ? sort : null + }; +} + +function sort(order, {sort, ...options} = {}) { + return { + ...(isOptions(order) && order.channel !== undefined ? initializer : apply)(options, sortTransform(order)), + sort: isDomainSort(sort) ? sort : null + }; +} + +function sortTransform(value) { + return (typeof value === "function" && value.length !== 1 ? sortData : sortValue)(value); +} + +function sortData(compare) { + return (data, facets) => { + const compareData = (i, j) => compare(data[i], data[j]); + return {data, facets: facets.map((I) => I.slice().sort(compareData))}; + }; +} + +function sortValue(value) { + let channel, order; + ({channel, value, order} = {...maybeValue(value)}); + const negate = channel?.startsWith("-"); + if (negate) channel = channel.slice(1); + if (order === undefined) order = negate ? descendingDefined : ascendingDefined; + if (typeof order !== "function") { + switch (`${order}`.toLowerCase()) { + case "ascending": + order = ascendingDefined; + break; + case "descending": + order = descendingDefined; + break; + default: + throw new Error(`invalid order: ${order}`); + } + } + return (data, facets, channels) => { + let V; + if (channel === undefined) { + V = valueof(data, value); + } else { + if (channels === undefined) throw new Error("channel sort requires an initializer"); + V = channels[channel]; + if (!V) return {}; // ignore missing channel + V = V.value; + } + const compareValue = (i, j) => order(V[i], V[j]); + return {data, facets: facets.map((I) => I.slice().sort(compareValue))}; + }; +} + +// Group on {z, fill, stroke}. +function groupZ$1(outputs, options) { + return groupn(null, null, outputs, options); +} + +// Group on {z, fill, stroke}, then on x. +function groupX(outputs = {y: "count"}, options = {}) { + const {x = identity$1} = options; + if (x == null) throw new Error("missing channel: x"); + return groupn(x, null, outputs, options); +} + +// Group on {z, fill, stroke}, then on y. +function groupY(outputs = {x: "count"}, options = {}) { + const {y = identity$1} = options; + if (y == null) throw new Error("missing channel: y"); + return groupn(null, y, outputs, options); +} + +// Group on {z, fill, stroke}, then on x and y. +function group(outputs = {fill: "count"}, options = {}) { + let {x, y} = options; + [x, y] = maybeTuple(x, y); + if (x == null) throw new Error("missing channel: x"); + if (y == null) throw new Error("missing channel: y"); + return groupn(x, y, outputs, options); +} + +function groupn( + x, // optionally group on x + y, // optionally group on y + { + data: reduceData = reduceIdentity, + filter, + sort, + reverse, + ...outputs // output channel definitions + } = {}, + inputs = {} // input channels and options +) { + // Compute the outputs. + outputs = maybeGroupOutputs(outputs, inputs); + reduceData = maybeGroupReduce(reduceData, identity$1); + sort = sort == null ? undefined : maybeGroupOutput("sort", sort, inputs); + filter = filter == null ? undefined : maybeGroupEvaluator("filter", filter, inputs); + + // Produce x and y output channels as appropriate. + const [GX, setGX] = maybeColumn(x); + const [GY, setGY] = maybeColumn(y); + + // Greedily materialize the z, fill, and stroke channels (if channels and not + // constants) so that we can reference them for subdividing groups without + // computing them more than once. + const { + z, + fill, + stroke, + x1, + x2, // consumed if x is an output + y1, + y2, // consumed if y is an output + ...options + } = inputs; + const [GZ, setGZ] = maybeColumn(z); + const [vfill] = maybeColorChannel(fill); + const [vstroke] = maybeColorChannel(stroke); + const [GF, setGF] = maybeColumn(vfill); + const [GS, setGS] = maybeColumn(vstroke); + + return { + ...("z" in inputs && {z: GZ || z}), + ...("fill" in inputs && {fill: GF || fill}), + ...("stroke" in inputs && {stroke: GS || stroke}), + ...basic(options, (data, facets, plotOptions) => { + const X = maybeApplyInterval(valueof(data, x), plotOptions?.x); + const Y = maybeApplyInterval(valueof(data, y), plotOptions?.y); + const Z = valueof(data, z); + const F = valueof(data, vfill); + const S = valueof(data, vstroke); + const G = maybeSubgroup(outputs, {z: Z, fill: F, stroke: S}); + const groupFacets = []; + const groupData = []; + const GX = X && setGX([]); + const GY = Y && setGY([]); + const GZ = Z && setGZ([]); + const GF = F && setGF([]); + const GS = S && setGS([]); + let i = 0; + for (const o of outputs) o.initialize(data); + if (sort) sort.initialize(data); + if (filter) filter.initialize(data); + for (const facet of facets) { + const groupFacet = []; + for (const o of outputs) o.scope("facet", facet); + if (sort) sort.scope("facet", facet); + if (filter) filter.scope("facet", facet); + for (const [f, I] of maybeGroup(facet, G)) { + for (const [y, gg] of maybeGroup(I, Y)) { + for (const [x, g] of maybeGroup(gg, X)) { + const extent = {data}; + if (X) extent.x = x; + if (Y) extent.y = y; + if (filter && !filter.reduce(g, extent)) continue; + groupFacet.push(i++); + groupData.push(reduceData.reduceIndex(g, data, extent)); + if (X) GX.push(x); + if (Y) GY.push(y); + if (Z) GZ.push(G === Z ? f : Z[g[0]]); + if (F) GF.push(G === F ? f : F[g[0]]); + if (S) GS.push(G === S ? f : S[g[0]]); + for (const o of outputs) o.reduce(g, extent); + if (sort) sort.reduce(g, extent); + } + } + } + groupFacets.push(groupFacet); + } + maybeSort(groupFacets, sort, reverse); + return {data: groupData, facets: groupFacets}; + }), + ...(!hasOutput(outputs, "x") && (GX ? {x: GX} : {x1, x2})), + ...(!hasOutput(outputs, "y") && (GY ? {y: GY} : {y1, y2})), + ...Object.fromEntries(outputs.map(({name, output}) => [name, output])) + }; +} + +function hasOutput(outputs, ...names) { + for (const {name} of outputs) { + if (names.includes(name)) { + return true; + } + } + return false; +} + +function maybeOutputs(outputs, inputs, asOutput = maybeOutput) { + const entries = Object.entries(outputs); + // Propagate standard mark channels by default. + if (inputs.title != null && outputs.title === undefined) entries.push(["title", reduceTitle]); + if (inputs.href != null && outputs.href === undefined) entries.push(["href", reduceFirst$1]); + return entries + .filter(([, reduce]) => reduce !== undefined) + .map(([name, reduce]) => (reduce === null ? nullOutput(name) : asOutput(name, reduce, inputs))); +} + +function maybeOutput(name, reduce, inputs, asEvaluator = maybeEvaluator) { + let scale; // optional per-channel scale override + if (isObject(reduce) && "reduce" in reduce) (scale = reduce.scale), (reduce = reduce.reduce); // N.B. array.reduce + const evaluator = asEvaluator(name, reduce, inputs); + const [output, setOutput] = column(evaluator.label); + let O; + return { + name, + output: scale === undefined ? output : {value: output, scale}, + initialize(data) { + evaluator.initialize(data); + O = setOutput([]); + }, + scope(scope, I) { + evaluator.scope(scope, I); + }, + reduce(I, extent) { + O.push(evaluator.reduce(I, extent)); + } + }; +} + +function nullOutput(name) { + return {name, initialize() {}, scope() {}, reduce() {}}; +} + +function maybeEvaluator(name, reduce, inputs, asReduce = maybeReduce$1) { + const input = maybeInput(name, inputs); + const reducer = asReduce(reduce, input); + let V, context; + return { + label: labelof(reducer === reduceCount ? null : input, reducer.label), + initialize(data) { + V = input === undefined ? data : valueof(data, input); + if (reducer.scope === "data") { + context = reducer.reduceIndex(range(data), V); + } + }, + scope(scope, I) { + if (reducer.scope === scope) { + context = reducer.reduceIndex(I, V); + } + }, + reduce(I, extent) { + return reducer.scope == null ? reducer.reduceIndex(I, V, extent) : reducer.reduceIndex(I, V, context, extent); + } + }; +} + +function maybeGroup(I, X) { + return X + ? d3.sort( + d3.group(I, (i) => X[i]), + first + ) + : [[, I]]; +} + +function maybeReduce$1(reduce, value, fallback = invalidReduce) { + if (reduce == null) return fallback(reduce); + if (typeof reduce.reduceIndex === "function") return reduce; + if (typeof reduce.reduce === "function" && isObject(reduce)) return reduceReduce(reduce); // N.B. array.reduce + if (typeof reduce === "function") return reduceFunction(reduce); + if (/^p\d{2}$/i.test(reduce)) return reduceAccessor$1(percentile(reduce)); + switch (`${reduce}`.toLowerCase()) { + case "first": + return reduceFirst$1; + case "last": + return reduceLast$1; + case "identity": + return reduceIdentity; + case "count": + return reduceCount; + case "distinct": + return reduceDistinct; + case "sum": + return value == null ? reduceCount : reduceSum$1; + case "proportion": + return reduceProportion(value, "data"); + case "proportion-facet": + return reduceProportion(value, "facet"); + case "deviation": + return reduceAccessor$1(d3.deviation); + case "min": + return reduceAccessor$1(d3.min); + case "min-index": + return reduceAccessor$1(d3.minIndex); + case "max": + return reduceAccessor$1(d3.max); + case "max-index": + return reduceAccessor$1(d3.maxIndex); + case "mean": + return reduceMaybeTemporalAccessor(d3.mean); + case "median": + return reduceMaybeTemporalAccessor(d3.median); + case "variance": + return reduceAccessor$1(d3.variance); + case "mode": + return reduceAccessor$1(d3.mode); + } + return fallback(reduce); +} + +function invalidReduce(reduce) { + throw new Error(`invalid reduce: ${reduce}`); +} + +function maybeGroupOutputs(outputs, inputs) { + return maybeOutputs(outputs, inputs, maybeGroupOutput); +} + +function maybeGroupOutput(name, reduce, inputs) { + return maybeOutput(name, reduce, inputs, maybeGroupEvaluator); +} + +function maybeGroupEvaluator(name, reduce, inputs) { + return maybeEvaluator(name, reduce, inputs, maybeGroupReduce); +} + +function maybeGroupReduce(reduce, value) { + return maybeReduce$1(reduce, value, maybeGroupReduceFallback); +} + +function maybeGroupReduceFallback(reduce) { + switch (`${reduce}`.toLowerCase()) { + case "x": + return reduceX$1; + case "y": + return reduceY$1; + } + throw new Error(`invalid group reduce: ${reduce}`); +} + +function maybeSubgroup(outputs, inputs) { + for (const name in inputs) { + const value = inputs[name]; + if (value !== undefined && !outputs.some((o) => o.name === name)) { + return value; + } + } +} + +function maybeSort(facets, sort, reverse) { + if (sort) { + const S = sort.output.transform(); + const compare = (i, j) => ascendingDefined(S[i], S[j]); + facets.forEach((f) => f.sort(compare)); + } + if (reverse) { + facets.forEach((f) => f.reverse()); + } +} + +function reduceReduce(reduce) { + console.warn("deprecated reduce interface; implement reduceIndex instead."); + return {...reduce, reduceIndex: reduce.reduce.bind(reduce)}; +} + +function reduceFunction(f) { + return { + reduceIndex(I, X, extent) { + return f(take(X, I), extent); + } + }; +} + +function reduceAccessor$1(f) { + return { + reduceIndex(I, X) { + return f(I, (i) => X[i]); + } + }; +} + +function reduceMaybeTemporalAccessor(f) { + return { + reduceIndex(I, X) { + const x = f(I, (i) => X[i]); + return isTemporal(X) ? new Date(x) : x; + } + }; +} + +const reduceIdentity = { + reduceIndex(I, X) { + return take(X, I); + } +}; + +const reduceFirst$1 = { + reduceIndex(I, X) { + return X[I[0]]; + } +}; + +const reduceTitle = { + reduceIndex(I, X) { + const n = 5; + const groups = d3.sort( + d3.rollup( + I, + (V) => V.length, + (i) => X[i] + ), + second + ); + const top = groups.slice(-n).reverse(); + if (top.length < groups.length) { + const bottom = groups.slice(0, 1 - n); + top[n - 1] = [`… ${bottom.length.toLocaleString("en-US")} more`, d3.sum(bottom, second)]; + } + return top.map(([key, value]) => `${key} (${value.toLocaleString("en-US")})`).join("\n"); + } +}; + +const reduceLast$1 = { + reduceIndex(I, X) { + return X[I[I.length - 1]]; + } +}; + +const reduceCount = { + label: "Frequency", + reduceIndex(I) { + return I.length; + } +}; + +const reduceDistinct = { + label: "Distinct", + reduceIndex(I, X) { + const s = new d3.InternSet(); + for (const i of I) s.add(X[i]); + return s.size; + } +}; + +const reduceSum$1 = reduceAccessor$1(d3.sum); + +function reduceProportion(value, scope) { + return value == null + ? {scope, label: "Frequency", reduceIndex: (I, V, basis = 1) => I.length / basis} + : {scope, reduceIndex: (I, V, basis = 1) => d3.sum(I, (i) => V[i]) / basis}; +} + +const reduceX$1 = { + reduceIndex(I, X, {x}) { + return x; + } +}; + +const reduceY$1 = { + reduceIndex(I, X, {y}) { + return y; + } +}; + +function find(test) { + if (typeof test !== "function") throw new Error(`invalid test function: ${test}`); + return { + reduceIndex(I, V, {data}) { + return V[I.find((i) => test(data[i], i, data))]; + } + }; +} + +function createChannel(data, {scale, type, value, filter, hint, label = labelof(value)}, name) { + if (hint === undefined && typeof value?.transform === "function") hint = value.hint; + return inferChannelScale(name, { + scale, + type, + value: valueof(data, value), + label, + filter, + hint + }); +} + +function createChannels(channels, data) { + return Object.fromEntries( + Object.entries(channels).map(([name, channel]) => [name, createChannel(data, channel, name)]) + ); +} + +// TODO Use Float64Array for scales with numeric ranges, e.g. position? +function valueObject(channels, scales) { + const values = Object.fromEntries( + Object.entries(channels).map(([name, {scale: scaleName, value}]) => { + const scale = scaleName == null ? null : scales[scaleName]; + return [name, scale == null ? value : map$1(value, scale)]; + }) + ); + values.channels = channels; // expose channel state for advanced usage + return values; +} + +// If the channel uses the "auto" scale (or equivalently true), infer the scale +// from the channel name and the provided values. For color and symbol channels, +// no scale is applied if the values are literal; however for symbols, we must +// promote symbol names (e.g., "plus") to symbol implementations (symbolPlus). +// Note: mutates channel! +function inferChannelScale(name, channel) { + const {scale, value} = channel; + if (scale === true || scale === "auto") { + switch (name) { + case "fill": + case "stroke": + case "color": + channel.scale = scale !== true && isEvery(value, isColor) ? null : "color"; + break; + case "fillOpacity": + case "strokeOpacity": + case "opacity": + channel.scale = scale !== true && isEvery(value, isOpacity) ? null : "opacity"; + break; + case "symbol": + if (scale !== true && isEvery(value, isSymbol)) { + channel.scale = null; + channel.value = map$1(value, maybeSymbol); + } else { + channel.scale = "symbol"; + } + break; + default: + channel.scale = registry.has(name) ? name : null; + break; + } + } else if (scale === false) { + channel.scale = null; + } else if (scale != null && !registry.has(scale)) { + throw new Error(`unknown scale: ${scale}`); + } + return channel; +} + +// Note: mutates channel.domain! This is set to a function so that it is lazily +// computed; i.e., if the scale’s domain is set explicitly, that takes priority +// over the sort option, and we don’t need to do additional work. +function channelDomain(data, facets, channels, facetChannels, options) { + const {order: defaultOrder, reverse: defaultReverse, reduce: defaultReduce = true, limit: defaultLimit} = options; + for (const x in options) { + if (!registry.has(x)) continue; // ignore unknown scale keys (including generic options) + let {value: y, order = defaultOrder, reverse = defaultReverse, reduce = defaultReduce, limit = defaultLimit} = maybeValue(options[x]); // prettier-ignore + const negate = y?.startsWith("-"); + if (negate) y = y.slice(1); + order = order === undefined ? negate !== (y === "width" || y === "height") ? descendingGroup : ascendingGroup : maybeOrder$1(order); // prettier-ignore + if (reduce == null || reduce === false) continue; // disabled reducer + const X = x === "fx" || x === "fy" ? reindexFacetChannel(facets, facetChannels[x]) : findScaleChannel(channels, x); + if (!X) throw new Error(`missing channel for scale: ${x}`); + const XV = X.value; + const [lo = 0, hi = Infinity] = isIterable(limit) ? limit : limit < 0 ? [limit] : [0, limit]; + if (y == null) { + X.domain = () => { + let domain = Array.from(new d3.InternSet(XV)); // remove any duplicates + if (reverse) domain = domain.reverse(); + if (lo !== 0 || hi !== Infinity) domain = domain.slice(lo, hi); + return domain; + }; + } else { + const YV = + y === "data" + ? data + : y === "height" + ? difference(channels, "y1", "y2") + : y === "width" + ? difference(channels, "x1", "x2") + : values(channels, y, y === "y" ? "y2" : y === "x" ? "x2" : undefined); + const reducer = maybeReduce$1(reduce === true ? "max" : reduce, YV); + X.domain = () => { + let domain = d3.rollups( + range(XV), + (I) => reducer.reduceIndex(I, YV), + (i) => XV[i] + ); + if (order) domain.sort(order); + if (reverse) domain.reverse(); + if (lo !== 0 || hi !== Infinity) domain = domain.slice(lo, hi); + return domain.map(first); + }; + } + } +} + +function findScaleChannel(channels, scale) { + for (const name in channels) { + const channel = channels[name]; + if (channel.scale === scale) return channel; + } +} + +// Facet channels are not affected by transforms; so, to compute the domain of a +// facet scale, we must first re-index the facet channel according to the +// transformed mark index. Note: mutates channel, but that should be safe here? +function reindexFacetChannel(facets, channel) { + const originalFacets = facets.original; + if (originalFacets === facets) return channel; // not transformed + const V1 = channel.value; + const V2 = (channel.value = []); // mutates channel! + for (let i = 0; i < originalFacets.length; ++i) { + const vi = V1[originalFacets[i][0]]; + for (const j of facets[i]) V2[j] = vi; + } + return channel; +} + +function difference(channels, k1, k2) { + const X1 = values(channels, k1); + const X2 = values(channels, k2); + return map$1(X2, (x2, i) => Math.abs(x2 - X1[i]), Float64Array); +} + +function values(channels, name, alias) { + let channel = channels[name]; + if (!channel && alias !== undefined) channel = channels[alias]; + if (channel) return channel.value; + throw new Error(`missing channel: ${name}`); +} + +function maybeOrder$1(order) { + if (order == null || typeof order === "function") return order; + switch (`${order}`.toLowerCase()) { + case "ascending": + return ascendingGroup; + case "descending": + return descendingGroup; + } + throw new Error(`invalid order: ${order}`); +} + +function ascendingGroup([ak, av], [bk, bv]) { + return ascendingDefined(av, bv) || ascendingDefined(ak, bk); +} + +function descendingGroup([ak, av], [bk, bv]) { + return descendingDefined(av, bv) || ascendingDefined(ak, bk); +} + +function getSource(channels, key) { + let channel = channels[key]; + if (!channel) return; + while (channel.source) channel = channel.source; + return channel.source === null ? null : channel; +} + +function memoize1(compute) { + let cacheValue, cacheKeys; + return (...keys) => { + if (cacheKeys?.length !== keys.length || cacheKeys.some((k, i) => k !== keys[i])) { + cacheKeys = keys; + cacheValue = compute(...keys); + } + return cacheValue; + }; +} + +const numberFormat = memoize1((locale) => { + return new Intl.NumberFormat(locale); +}); + +const monthFormat = memoize1((locale, month) => { + return new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...(month && {month})}); +}); + +const weekdayFormat = memoize1((locale, weekday) => { + return new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...(weekday && {weekday})}); +}); + +function formatNumber(locale = "en-US") { + const format = numberFormat(locale); + return (i) => (i != null && !isNaN(i) ? format.format(i) : undefined); +} + +function formatMonth(locale = "en-US", format = "short") { + const fmt = monthFormat(locale, format); + return (i) => (i != null && !isNaN((i = +new Date(Date.UTC(2000, +i)))) ? fmt.format(i) : undefined); +} + +function formatWeekday(locale = "en-US", format = "short") { + const fmt = weekdayFormat(locale, format); + return (i) => (i != null && !isNaN((i = +new Date(Date.UTC(2001, 0, +i)))) ? fmt.format(i) : undefined); +} + +function formatIsoDate(date) { + return format(date, "Invalid Date"); +} + +function formatAuto(locale = "en-US") { + const number = formatNumber(locale); + return (v) => (v instanceof Date ? formatIsoDate : typeof v === "number" ? number : string)(v); +} + +// TODO When Plot supports a top-level locale option, this should be removed +// because it lacks context to know which locale to use; formatAuto should be +// used instead whenever possible. +const formatDefault = formatAuto(); + +let warnings = 0; +let lastMessage; + +function consumeWarnings() { + const w = warnings; + warnings = 0; + lastMessage = undefined; + return w; +} + +function warn(message) { + if (message === lastMessage) return; + lastMessage = message; + console.warn(message); + ++warnings; +} + +const offset = (typeof window !== "undefined" ? window.devicePixelRatio > 1 : typeof it === "undefined") ? 0 : 0.5; // prettier-ignore + +let nextClipId = 0; + +function getClipId() { + return `plot-clip-${++nextClipId}`; +} + +function styles( + mark, + { + title, + href, + ariaLabel: variaLabel, + ariaDescription, + ariaHidden, + target, + fill, + fillOpacity, + stroke, + strokeWidth, + strokeOpacity, + strokeLinejoin, + strokeLinecap, + strokeMiterlimit, + strokeDasharray, + strokeDashoffset, + opacity, + mixBlendMode, + imageFilter, + paintOrder, + pointerEvents, + shapeRendering, + channels + }, + { + ariaLabel: cariaLabel, + fill: defaultFill = "currentColor", + fillOpacity: defaultFillOpacity, + stroke: defaultStroke = "none", + strokeOpacity: defaultStrokeOpacity, + strokeWidth: defaultStrokeWidth, + strokeLinecap: defaultStrokeLinecap, + strokeLinejoin: defaultStrokeLinejoin, + strokeMiterlimit: defaultStrokeMiterlimit, + paintOrder: defaultPaintOrder + } +) { + // Some marks don’t support fill (e.g., tick and rule). + if (defaultFill === null) { + fill = null; + fillOpacity = null; + } + + // Some marks don’t support stroke (e.g., image). + if (defaultStroke === null) { + stroke = null; + strokeOpacity = null; + } + + // Some marks default to fill with no stroke, while others default to stroke + // with no fill. For example, bar and area default to fill, while dot and line + // default to stroke. For marks that fill by default, the default fill only + // applies if the stroke is (constant) none; if you set a stroke, then the + // default fill becomes none. Similarly for marks that stroke by stroke, the + // default stroke only applies if the fill is (constant) none. + if (isNoneish(defaultFill)) { + if (!isNoneish(defaultStroke) && (!isNoneish(fill) || channels?.fill)) defaultStroke = "none"; + } else { + if (isNoneish(defaultStroke) && (!isNoneish(stroke) || channels?.stroke)) defaultFill = "none"; + } + + const [vfill, cfill] = maybeColorChannel(fill, defaultFill); + const [vfillOpacity, cfillOpacity] = maybeNumberChannel(fillOpacity, defaultFillOpacity); + const [vstroke, cstroke] = maybeColorChannel(stroke, defaultStroke); + const [vstrokeOpacity, cstrokeOpacity] = maybeNumberChannel(strokeOpacity, defaultStrokeOpacity); + const [vopacity, copacity] = maybeNumberChannel(opacity); + + // For styles that have no effect if there is no stroke, only apply the + // defaults if the stroke is not the constant none. (If stroke is a channel, + // then cstroke will be undefined, but there’s still a stroke; hence we don’t + // use isNoneish here.) + if (!isNone(cstroke)) { + if (strokeWidth === undefined) strokeWidth = defaultStrokeWidth; + if (strokeLinecap === undefined) strokeLinecap = defaultStrokeLinecap; + if (strokeLinejoin === undefined) strokeLinejoin = defaultStrokeLinejoin; + + // The default stroke miterlimit need not be applied if the current stroke + // is the constant round; this only has effect on miter joins. + if (strokeMiterlimit === undefined && !isRound(strokeLinejoin)) strokeMiterlimit = defaultStrokeMiterlimit; + + // The paint order only takes effect if there is both a fill and a stroke + // (at least if we ignore markers, which no built-in marks currently use). + if (!isNone(cfill) && paintOrder === undefined) paintOrder = defaultPaintOrder; + } + + const [vstrokeWidth, cstrokeWidth] = maybeNumberChannel(strokeWidth); + + // Some marks don’t support fill (e.g., tick and rule). + if (defaultFill !== null) { + mark.fill = impliedString(cfill, "currentColor"); + mark.fillOpacity = impliedNumber(cfillOpacity, 1); + } + + // Some marks don’t support stroke (e.g., image). + if (defaultStroke !== null) { + mark.stroke = impliedString(cstroke, "none"); + mark.strokeWidth = impliedNumber(cstrokeWidth, 1); + mark.strokeOpacity = impliedNumber(cstrokeOpacity, 1); + mark.strokeLinejoin = impliedString(strokeLinejoin, "miter"); + mark.strokeLinecap = impliedString(strokeLinecap, "butt"); + mark.strokeMiterlimit = impliedNumber(strokeMiterlimit, 4); + mark.strokeDasharray = impliedString(strokeDasharray, "none"); + mark.strokeDashoffset = impliedString(strokeDashoffset, "0"); + } + + mark.target = string(target); + mark.ariaLabel = string(cariaLabel); + mark.ariaDescription = string(ariaDescription); + mark.ariaHidden = string(ariaHidden); + mark.opacity = impliedNumber(copacity, 1); + mark.mixBlendMode = impliedString(mixBlendMode, "normal"); + mark.imageFilter = impliedString(imageFilter, "none"); + mark.paintOrder = impliedString(paintOrder, "normal"); + mark.pointerEvents = impliedString(pointerEvents, "auto"); + mark.shapeRendering = impliedString(shapeRendering, "auto"); + + return { + title: {value: title, optional: true, filter: null}, + href: {value: href, optional: true, filter: null}, + ariaLabel: {value: variaLabel, optional: true, filter: null}, + fill: {value: vfill, scale: "auto", optional: true}, + fillOpacity: {value: vfillOpacity, scale: "auto", optional: true}, + stroke: {value: vstroke, scale: "auto", optional: true}, + strokeOpacity: {value: vstrokeOpacity, scale: "auto", optional: true}, + strokeWidth: {value: vstrokeWidth, optional: true}, + opacity: {value: vopacity, scale: "auto", optional: true} + }; +} + +// Applies the specified titles via selection.call. +function applyTitle(selection, L) { + if (L) + selection + .filter((i) => nonempty(L[i])) + .append("title") + .call(applyText, L); +} + +// Like applyTitle, but for grouped data (lines, areas). +function applyTitleGroup(selection, L) { + if (L) + selection + .filter(([i]) => nonempty(L[i])) + .append("title") + .call(applyTextGroup, L); +} + +function applyText(selection, T) { + if (T) selection.text((i) => formatDefault(T[i])); +} + +function applyTextGroup(selection, T) { + if (T) selection.text(([i]) => formatDefault(T[i])); +} + +function applyChannelStyles( + selection, + {target, tip}, + { + ariaLabel: AL, + title: T, + fill: F, + fillOpacity: FO, + stroke: S, + strokeOpacity: SO, + strokeWidth: SW, + opacity: O, + href: H + } +) { + if (AL) applyAttr(selection, "aria-label", (i) => AL[i]); + if (F) applyAttr(selection, "fill", (i) => F[i]); + if (FO) applyAttr(selection, "fill-opacity", (i) => FO[i]); + if (S) applyAttr(selection, "stroke", (i) => S[i]); + if (SO) applyAttr(selection, "stroke-opacity", (i) => SO[i]); + if (SW) applyAttr(selection, "stroke-width", (i) => SW[i]); + if (O) applyAttr(selection, "opacity", (i) => O[i]); + if (H) applyHref(selection, (i) => H[i], target); + if (!tip) applyTitle(selection, T); +} + +function applyGroupedChannelStyles( + selection, + {target, tip}, + { + ariaLabel: AL, + title: T, + fill: F, + fillOpacity: FO, + stroke: S, + strokeOpacity: SO, + strokeWidth: SW, + opacity: O, + href: H + } +) { + if (AL) applyAttr(selection, "aria-label", ([i]) => AL[i]); + if (F) applyAttr(selection, "fill", ([i]) => F[i]); + if (FO) applyAttr(selection, "fill-opacity", ([i]) => FO[i]); + if (S) applyAttr(selection, "stroke", ([i]) => S[i]); + if (SO) applyAttr(selection, "stroke-opacity", ([i]) => SO[i]); + if (SW) applyAttr(selection, "stroke-width", ([i]) => SW[i]); + if (O) applyAttr(selection, "opacity", ([i]) => O[i]); + if (H) applyHref(selection, ([i]) => H[i], target); + if (!tip) applyTitleGroup(selection, T); +} + +function groupAesthetics( + { + ariaLabel: AL, + title: T, + fill: F, + fillOpacity: FO, + stroke: S, + strokeOpacity: SO, + strokeWidth: SW, + opacity: O, + href: H + }, + {tip} +) { + return [AL, tip ? undefined : T, F, FO, S, SO, SW, O, H].filter((c) => c !== undefined); +} + +function groupZ(I, Z, z) { + const G = d3.group(I, (i) => Z[i]); + if (z === undefined && G.size > (1 + I.length) >> 1) { + warn( + `Warning: the implicit z channel has high cardinality. This may occur when the fill or stroke channel is associated with quantitative data rather than ordinal or categorical data. You can suppress this warning by setting the z option explicitly; if this data represents a single series, set z to null.` + ); + } + return G.values(); +} + +function* groupIndex(I, position, mark, channels) { + const {z} = mark; + const {z: Z} = channels; // group channel + const A = groupAesthetics(channels, mark); // aesthetic channels + const C = [...position, ...A]; // all channels + + // Group the current index by Z (if any). + for (const G of Z ? groupZ(I, Z, z) : [I]) { + let Ag; // the A-values (aesthetics) of the current group, if any + let Gg; // the current group index (a subset of G, and I), if any + out: for (const i of G) { + // If any channel has an undefined value for this index, skip it. + for (const c of C) { + if (!defined(c[i])) { + if (Gg) Gg.push(-1); + continue out; + } + } + + // Otherwise, if this is a new group, record the aesthetics for this + // group. Yield the current group and start a new one. + if (Ag === undefined) { + if (Gg) yield Gg; + (Ag = A.map((c) => keyof(c[i]))), (Gg = [i]); + continue; + } + + // Otherwise, add the current index to the current group. Then, if any of + // the aesthetics don’t match the current group, yield the current group + // and start a new group of the current index. + Gg.push(i); + for (let j = 0; j < A.length; ++j) { + const k = keyof(A[j][i]); + if (k !== Ag[j]) { + yield Gg; + (Ag = A.map((c) => keyof(c[i]))), (Gg = [i]); + continue out; + } + } + } + + // Yield the current group, if any. + if (Gg) yield Gg; + } +} + +// TODO Accept other types of clips (paths, urls, x, y, other marks…)? +// https://github.com/observablehq/plot/issues/181 +function maybeClip(clip) { + if (clip === true) clip = "frame"; + else if (clip === false) clip = null; + else if (clip != null) clip = keyword(clip, "clip", ["frame", "sphere"]); + return clip; +} + +// Note: may mutate selection.node! +function applyClip(selection, mark, dimensions, context) { + let clipUrl; + const {clip = context.clip} = mark; + switch (clip) { + case "frame": { + const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions; + const id = getClipId(); + clipUrl = `url(#${id})`; + selection = create("svg:g", context) + .call((g) => + g + .append("svg:clipPath") + .attr("id", id) + .append("rect") + .attr("x", marginLeft) + .attr("y", marginTop) + .attr("width", width - marginRight - marginLeft) + .attr("height", height - marginTop - marginBottom) + ) + .each(function () { + this.appendChild(selection.node()); + selection.node = () => this; // Note: mutation! + }); + break; + } + case "sphere": { + const {projection} = context; + if (!projection) throw new Error(`the "sphere" clip option requires a projection`); + const id = getClipId(); + clipUrl = `url(#${id})`; + selection + .append("clipPath") + .attr("id", id) + .append("path") + .attr("d", d3.geoPath(projection)({type: "Sphere"})); + break; + } + } + // Here we’re careful to apply the ARIA attributes to the outer G element when + // clipping is applied, and to apply the ARIA attributes before any other + // attributes (for readability). + applyAttr(selection, "aria-label", mark.ariaLabel); + applyAttr(selection, "aria-description", mark.ariaDescription); + applyAttr(selection, "aria-hidden", mark.ariaHidden); + applyAttr(selection, "clip-path", clipUrl); +} + +// Note: may mutate selection.node! +function applyIndirectStyles(selection, mark, dimensions, context) { + applyClip(selection, mark, dimensions, context); + applyAttr(selection, "fill", mark.fill); + applyAttr(selection, "fill-opacity", mark.fillOpacity); + applyAttr(selection, "stroke", mark.stroke); + applyAttr(selection, "stroke-width", mark.strokeWidth); + applyAttr(selection, "stroke-opacity", mark.strokeOpacity); + applyAttr(selection, "stroke-linejoin", mark.strokeLinejoin); + applyAttr(selection, "stroke-linecap", mark.strokeLinecap); + applyAttr(selection, "stroke-miterlimit", mark.strokeMiterlimit); + applyAttr(selection, "stroke-dasharray", mark.strokeDasharray); + applyAttr(selection, "stroke-dashoffset", mark.strokeDashoffset); + applyAttr(selection, "shape-rendering", mark.shapeRendering); + applyAttr(selection, "filter", mark.imageFilter); + applyAttr(selection, "paint-order", mark.paintOrder); + const {pointerEvents = context.pointerSticky === false ? "none" : undefined} = mark; + applyAttr(selection, "pointer-events", pointerEvents); +} + +function applyDirectStyles(selection, mark) { + applyStyle(selection, "mix-blend-mode", mark.mixBlendMode); + applyAttr(selection, "opacity", mark.opacity); +} + +function applyHref(selection, href, target) { + selection.each(function (i) { + const h = href(i); + if (h != null) { + const a = this.ownerDocument.createElementNS(d3.namespaces.svg, "a"); + a.setAttribute("fill", "inherit"); + a.setAttributeNS(d3.namespaces.xlink, "href", h); + if (target != null) a.setAttribute("target", target); + this.parentNode.insertBefore(a, this).appendChild(this); + } + }); +} + +function applyAttr(selection, name, value) { + if (value != null) selection.attr(name, value); +} + +function applyStyle(selection, name, value) { + if (value != null) selection.style(name, value); +} + +function applyTransform(selection, mark, {x, y}, tx = offset, ty = offset) { + tx += mark.dx; + ty += mark.dy; + if (x?.bandwidth) tx += x.bandwidth() / 2; + if (y?.bandwidth) ty += y.bandwidth() / 2; + if (tx || ty) selection.attr("transform", `translate(${tx},${ty})`); +} + +function impliedString(value, impliedValue) { + if ((value = string(value)) !== impliedValue) return value; +} + +function impliedNumber(value, impliedValue) { + if ((value = number$1(value)) !== impliedValue) return value; +} + +// https://www.w3.org/TR/CSS21/grammar.html +const validClassName = + /^-?([_a-z]|[\240-\377]|\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])([_a-z0-9-]|[\240-\377]|\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])*$/i; + +function maybeClassName(name) { + // The default should be changed whenever the default styles are changed, so + // as to avoid conflict when multiple versions of Plot are on the page. + if (name === undefined) return "plot-d6a7b5"; + name = `${name}`; + if (!validClassName.test(name)) throw new Error(`invalid class name: ${name}`); + return name; +} + +function applyInlineStyles(selection, style) { + if (typeof style === "string") { + selection.property("style", style); + } else if (style != null) { + for (const element of selection) { + Object.assign(element.style, style); + } + } +} + +function applyFrameAnchor({frameAnchor}, {width, height, marginTop, marginRight, marginBottom, marginLeft}) { + return [ + /left$/.test(frameAnchor) + ? marginLeft + : /right$/.test(frameAnchor) + ? width - marginRight + : (marginLeft + width - marginRight) / 2, + /^top/.test(frameAnchor) + ? marginTop + : /^bottom/.test(frameAnchor) + ? height - marginBottom + : (marginTop + height - marginBottom) / 2 + ]; +} + +function createContext(options = {}) { + const {document = typeof window !== "undefined" ? window.document : undefined, clip} = options; + return {document, clip: maybeClip(clip)}; +} + +function create(name, {document}) { + return d3.select(d3.creator(name).call(document.documentElement)); +} + +const pi = Math.PI; +const tau = 2 * pi; +const defaultAspectRatio = 0.618; + +function createProjection( + { + projection, + inset: globalInset = 0, + insetTop = globalInset, + insetRight = globalInset, + insetBottom = globalInset, + insetLeft = globalInset + } = {}, + dimensions +) { + if (projection == null) return; + if (typeof projection.stream === "function") return projection; // d3 projection + let options; + let domain; + let clip = "frame"; + + // If the projection was specified as an object with additional options, + // extract those. The order of precedence for insetTop (and other insets) is: + // projection.insetTop, projection.inset, (global) insetTop, (global) inset. + // Any other options on this object will be passed through to the initializer. + if (isObject(projection)) { + let inset; + ({ + type: projection, + domain, + inset, + insetTop = inset !== undefined ? inset : insetTop, + insetRight = inset !== undefined ? inset : insetRight, + insetBottom = inset !== undefined ? inset : insetBottom, + insetLeft = inset !== undefined ? inset : insetLeft, + clip = clip, + ...options + } = projection); + if (projection == null) return; + } + + // For named projections, retrieve the corresponding projection initializer. + if (typeof projection !== "function") ({type: projection} = namedProjection(projection)); + + // Compute the frame dimensions and invoke the projection initializer. + const {width, height, marginLeft, marginRight, marginTop, marginBottom} = dimensions; + const dx = width - marginLeft - marginRight - insetLeft - insetRight; + const dy = height - marginTop - marginBottom - insetTop - insetBottom; + projection = projection?.({width: dx, height: dy, clip, ...options}); + + // The projection initializer might decide to not use a projection. + if (projection == null) return; + clip = maybePostClip(clip, marginLeft, marginTop, width - marginRight, height - marginBottom); + + // Translate the origin to the top-left corner, respecting margins and insets. + let tx = marginLeft + insetLeft; + let ty = marginTop + insetTop; + let transform; + + // If a domain is specified, fit the projection to the frame. + if (domain != null) { + const [[x0, y0], [x1, y1]] = d3.geoPath(projection).bounds(domain); + const k = Math.min(dx / (x1 - x0), dy / (y1 - y0)); + if (k > 0) { + tx -= (k * (x0 + x1) - dx) / 2; + ty -= (k * (y0 + y1) - dy) / 2; + transform = d3.geoTransform({ + point(x, y) { + this.stream.point(x * k + tx, y * k + ty); + } + }); + } else { + warn(`Warning: the projection could not be fit to the specified domain; using the default scale.`); + } + } + + transform ??= + tx === 0 && ty === 0 + ? identity() + : d3.geoTransform({ + point(x, y) { + this.stream.point(x + tx, y + ty); + } + }); + + return {stream: (s) => projection.stream(transform.stream(clip(s)))}; +} + +function namedProjection(projection) { + switch (`${projection}`.toLowerCase()) { + case "albers-usa": + return scaleProjection$1(d3.geoAlbersUsa, 0.7463, 0.4673); + case "albers": + return conicProjection(d3.geoAlbers, 0.7463, 0.4673); + case "azimuthal-equal-area": + return scaleProjection$1(d3.geoAzimuthalEqualArea, 4, 4); + case "azimuthal-equidistant": + return scaleProjection$1(d3.geoAzimuthalEquidistant, tau, tau); + case "conic-conformal": + return conicProjection(d3.geoConicConformal, tau, tau); + case "conic-equal-area": + return conicProjection(d3.geoConicEqualArea, 6.1702, 2.9781); + case "conic-equidistant": + return conicProjection(d3.geoConicEquidistant, 7.312, 3.6282); + case "equal-earth": + return scaleProjection$1(d3.geoEqualEarth, 5.4133, 2.6347); + case "equirectangular": + return scaleProjection$1(d3.geoEquirectangular, tau, pi); + case "gnomonic": + return scaleProjection$1(d3.geoGnomonic, 3.4641, 3.4641); + case "identity": + return {type: identity}; + case "reflect-y": + return {type: reflectY}; + case "mercator": + return scaleProjection$1(d3.geoMercator, tau, tau); + case "orthographic": + return scaleProjection$1(d3.geoOrthographic, 2, 2); + case "stereographic": + return scaleProjection$1(d3.geoStereographic, 2, 2); + case "transverse-mercator": + return scaleProjection$1(d3.geoTransverseMercator, tau, tau); + default: + throw new Error(`unknown projection type: ${projection}`); + } +} + +function maybePostClip(clip, x1, y1, x2, y2) { + if (clip === false || clip == null || typeof clip === "number") return (s) => s; + if (clip === true) clip = "frame"; + switch (`${clip}`.toLowerCase()) { + case "frame": + return d3.geoClipRectangle(x1, y1, x2, y2); + default: + throw new Error(`unknown projection clip type: ${clip}`); + } +} + +function scaleProjection$1(createProjection, kx, ky) { + return { + type: ({width, height, rotate, precision = 0.15, clip}) => { + const projection = createProjection(); + if (precision != null) projection.precision?.(precision); + if (rotate != null) projection.rotate?.(rotate); + if (typeof clip === "number") projection.clipAngle?.(clip); + projection.scale(Math.min(width / kx, height / ky)); + projection.translate([width / 2, height / 2]); + return projection; + }, + aspectRatio: ky / kx + }; +} + +function conicProjection(createProjection, kx, ky) { + const {type, aspectRatio} = scaleProjection$1(createProjection, kx, ky); + return { + type: (options) => { + const {parallels, domain, width, height} = options; + const projection = type(options); + if (parallels != null) { + projection.parallels(parallels); + if (domain === undefined) { + projection.fitSize([width, height], {type: "Sphere"}); + } + } + return projection; + }, + aspectRatio + }; +} + +const identity = constant({stream: (stream) => stream}); + +const reflectY = constant( + d3.geoTransform({ + point(x, y) { + this.stream.point(x, -y); + } + }) +); + +// Applies a point-wise projection to the given paired x and y channels. +// Note: mutates values! +function project(cx, cy, values, projection) { + const x = values[cx]; + const y = values[cy]; + const n = x.length; + const X = (values[cx] = new Float64Array(n).fill(NaN)); + const Y = (values[cy] = new Float64Array(n).fill(NaN)); + let i; + const stream = projection.stream({ + point(x, y) { + X[i] = x; + Y[i] = y; + } + }); + for (i = 0; i < n; ++i) { + stream.point(x[i], y[i]); + } +} + +// Returns true if a projection was specified. This should match the logic of +// createProjection above, and is called before we construct the projection. +// (Though note that we ignore the edge case where the projection initializer +// may return null.) +function hasProjection({projection} = {}) { + if (projection == null) return false; + if (typeof projection.stream === "function") return true; + if (isObject(projection)) projection = projection.type; + return projection != null; +} + +// When a named projection is specified, we can use its natural aspect ratio to +// determine a good value for the projection’s height based on the desired +// width. When we don’t have a way to know, the golden ratio is our best guess. +// Due to a circular dependency (we need to know the height before we can +// construct the projection), we have to test the raw projection option rather +// than the materialized projection; therefore we must be extremely careful that +// the logic of this function exactly matches createProjection above! +function projectionAspectRatio(projection) { + if (typeof projection?.stream === "function") return defaultAspectRatio; + if (isObject(projection)) projection = projection.type; + if (projection == null) return; + if (typeof projection !== "function") { + const {aspectRatio} = namedProjection(projection); + if (aspectRatio) return aspectRatio; + } + return defaultAspectRatio; +} + +// Extract the (possibly) scaled values for the x and y channels, and apply the +// projection if any. +function applyPosition(channels, scales, {projection}) { + const {x, y} = channels; + let position = {}; + if (x) position.x = x; + if (y) position.y = y; + position = valueObject(position, scales); + if (projection && x?.scale === "x" && y?.scale === "y") project("x", "y", position, projection); + if (x) position.x = coerceNumbers(position.x); + if (y) position.y = coerceNumbers(position.y); + return position; +} + +function getGeometryChannels(channel) { + const X = []; + const Y = []; + const x = {scale: "x", value: X}; + const y = {scale: "y", value: Y}; + const sink = { + point(x, y) { + X.push(x); + Y.push(y); + }, + lineStart() {}, + lineEnd() {}, + polygonStart() {}, + polygonEnd() {}, + sphere() {} + }; + for (const object of channel.value) d3.geoStream(object, sink); + return [x, y]; +} + +const categoricalSchemes = new Map([ + ["accent", d3.schemeAccent], + ["category10", d3.schemeCategory10], + ["dark2", d3.schemeDark2], + ["paired", d3.schemePaired], + ["pastel1", d3.schemePastel1], + ["pastel2", d3.schemePastel2], + ["set1", d3.schemeSet1], + ["set2", d3.schemeSet2], + ["set3", d3.schemeSet3], + ["tableau10", d3.schemeTableau10] +]); + +function isCategoricalScheme(scheme) { + return scheme != null && categoricalSchemes.has(`${scheme}`.toLowerCase()); +} + +const ordinalSchemes = new Map([ + ...categoricalSchemes, + + // diverging + ["brbg", scheme11(d3.schemeBrBG, d3.interpolateBrBG)], + ["prgn", scheme11(d3.schemePRGn, d3.interpolatePRGn)], + ["piyg", scheme11(d3.schemePiYG, d3.interpolatePiYG)], + ["puor", scheme11(d3.schemePuOr, d3.interpolatePuOr)], + ["rdbu", scheme11(d3.schemeRdBu, d3.interpolateRdBu)], + ["rdgy", scheme11(d3.schemeRdGy, d3.interpolateRdGy)], + ["rdylbu", scheme11(d3.schemeRdYlBu, d3.interpolateRdYlBu)], + ["rdylgn", scheme11(d3.schemeRdYlGn, d3.interpolateRdYlGn)], + ["spectral", scheme11(d3.schemeSpectral, d3.interpolateSpectral)], + + // reversed diverging (for temperature data) + ["burd", scheme11r(d3.schemeRdBu, d3.interpolateRdBu)], + ["buylrd", scheme11r(d3.schemeRdYlBu, d3.interpolateRdYlBu)], + + // sequential (single-hue) + ["blues", scheme9(d3.schemeBlues, d3.interpolateBlues)], + ["greens", scheme9(d3.schemeGreens, d3.interpolateGreens)], + ["greys", scheme9(d3.schemeGreys, d3.interpolateGreys)], + ["oranges", scheme9(d3.schemeOranges, d3.interpolateOranges)], + ["purples", scheme9(d3.schemePurples, d3.interpolatePurples)], + ["reds", scheme9(d3.schemeReds, d3.interpolateReds)], + + // sequential (multi-hue) + ["turbo", schemei(d3.interpolateTurbo)], + ["viridis", schemei(d3.interpolateViridis)], + ["magma", schemei(d3.interpolateMagma)], + ["inferno", schemei(d3.interpolateInferno)], + ["plasma", schemei(d3.interpolatePlasma)], + ["cividis", schemei(d3.interpolateCividis)], + ["cubehelix", schemei(d3.interpolateCubehelixDefault)], + ["warm", schemei(d3.interpolateWarm)], + ["cool", schemei(d3.interpolateCool)], + ["bugn", scheme9(d3.schemeBuGn, d3.interpolateBuGn)], + ["bupu", scheme9(d3.schemeBuPu, d3.interpolateBuPu)], + ["gnbu", scheme9(d3.schemeGnBu, d3.interpolateGnBu)], + ["orrd", scheme9(d3.schemeOrRd, d3.interpolateOrRd)], + ["pubu", scheme9(d3.schemePuBu, d3.interpolatePuBu)], + ["pubugn", scheme9(d3.schemePuBuGn, d3.interpolatePuBuGn)], + ["purd", scheme9(d3.schemePuRd, d3.interpolatePuRd)], + ["rdpu", scheme9(d3.schemeRdPu, d3.interpolateRdPu)], + ["ylgn", scheme9(d3.schemeYlGn, d3.interpolateYlGn)], + ["ylgnbu", scheme9(d3.schemeYlGnBu, d3.interpolateYlGnBu)], + ["ylorbr", scheme9(d3.schemeYlOrBr, d3.interpolateYlOrBr)], + ["ylorrd", scheme9(d3.schemeYlOrRd, d3.interpolateYlOrRd)], + + // cyclical + ["rainbow", schemeicyclical(d3.interpolateRainbow)], + ["sinebow", schemeicyclical(d3.interpolateSinebow)] +]); + +function scheme9(scheme, interpolate) { + return ({length: n}) => { + if (n === 1) return [scheme[3][1]]; // favor midpoint + if (n === 2) return [scheme[3][1], scheme[3][2]]; // favor darker + n = Math.max(3, Math.floor(n)); + return n > 9 ? d3.quantize(interpolate, n) : scheme[n]; + }; +} + +function scheme11(scheme, interpolate) { + return ({length: n}) => { + if (n === 2) return [scheme[3][0], scheme[3][2]]; // favor diverging extrema + n = Math.max(3, Math.floor(n)); + return n > 11 ? d3.quantize(interpolate, n) : scheme[n]; + }; +} + +function scheme11r(scheme, interpolate) { + return ({length: n}) => { + if (n === 2) return [scheme[3][2], scheme[3][0]]; // favor diverging extrema + n = Math.max(3, Math.floor(n)); + return n > 11 ? d3.quantize((t) => interpolate(1 - t), n) : scheme[n].slice().reverse(); + }; +} + +function schemei(interpolate) { + return ({length: n}) => d3.quantize(interpolate, Math.max(2, Math.floor(n))); +} + +function schemeicyclical(interpolate) { + return ({length: n}) => d3.quantize(interpolate, Math.floor(n) + 1).slice(0, -1); +} + +function ordinalScheme(scheme) { + const s = `${scheme}`.toLowerCase(); + if (!ordinalSchemes.has(s)) throw new Error(`unknown ordinal scheme: ${s}`); + return ordinalSchemes.get(s); +} + +function ordinalRange(scheme, length) { + const s = ordinalScheme(scheme); + const r = typeof s === "function" ? s({length}) : s; + return r.length !== length ? r.slice(0, length) : r; +} + +// If the specified domain contains only booleans (ignoring null and undefined), +// returns a corresponding range where false is mapped to the low color and true +// is mapped to the high color of the specified scheme. +function maybeBooleanRange(domain, scheme = "greys") { + const range = new Set(); + const [f, t] = ordinalRange(scheme, 2); + for (const value of domain) { + if (value == null) continue; + if (value === true) range.add(t); + else if (value === false) range.add(f); + else return; + } + return [...range]; +} + +const quantitativeSchemes = new Map([ + // diverging + ["brbg", d3.interpolateBrBG], + ["prgn", d3.interpolatePRGn], + ["piyg", d3.interpolatePiYG], + ["puor", d3.interpolatePuOr], + ["rdbu", d3.interpolateRdBu], + ["rdgy", d3.interpolateRdGy], + ["rdylbu", d3.interpolateRdYlBu], + ["rdylgn", d3.interpolateRdYlGn], + ["spectral", d3.interpolateSpectral], + + // reversed diverging (for temperature data) + ["burd", (t) => d3.interpolateRdBu(1 - t)], + ["buylrd", (t) => d3.interpolateRdYlBu(1 - t)], + + // sequential (single-hue) + ["blues", d3.interpolateBlues], + ["greens", d3.interpolateGreens], + ["greys", d3.interpolateGreys], + ["purples", d3.interpolatePurples], + ["reds", d3.interpolateReds], + ["oranges", d3.interpolateOranges], + + // sequential (multi-hue) + ["turbo", d3.interpolateTurbo], + ["viridis", d3.interpolateViridis], + ["magma", d3.interpolateMagma], + ["inferno", d3.interpolateInferno], + ["plasma", d3.interpolatePlasma], + ["cividis", d3.interpolateCividis], + ["cubehelix", d3.interpolateCubehelixDefault], + ["warm", d3.interpolateWarm], + ["cool", d3.interpolateCool], + ["bugn", d3.interpolateBuGn], + ["bupu", d3.interpolateBuPu], + ["gnbu", d3.interpolateGnBu], + ["orrd", d3.interpolateOrRd], + ["pubugn", d3.interpolatePuBuGn], + ["pubu", d3.interpolatePuBu], + ["purd", d3.interpolatePuRd], + ["rdpu", d3.interpolateRdPu], + ["ylgnbu", d3.interpolateYlGnBu], + ["ylgn", d3.interpolateYlGn], + ["ylorbr", d3.interpolateYlOrBr], + ["ylorrd", d3.interpolateYlOrRd], + + // cyclical + ["rainbow", d3.interpolateRainbow], + ["sinebow", d3.interpolateSinebow] +]); + +function quantitativeScheme(scheme) { + const s = `${scheme}`.toLowerCase(); + if (!quantitativeSchemes.has(s)) throw new Error(`unknown quantitative scheme: ${s}`); + return quantitativeSchemes.get(s); +} + +const divergingSchemes = new Set([ + "brbg", + "prgn", + "piyg", + "puor", + "rdbu", + "rdgy", + "rdylbu", + "rdylgn", + "spectral", + "burd", + "buylrd" +]); + +function isDivergingScheme(scheme) { + return scheme != null && divergingSchemes.has(`${scheme}`.toLowerCase()); +} + +const flip = (i) => (t) => i(1 - t); +const unit = [0, 1]; + +const interpolators = new Map([ + // numbers + ["number", d3.interpolateNumber], + + // color spaces + ["rgb", d3.interpolateRgb], + ["hsl", d3.interpolateHsl], + ["hcl", d3.interpolateHcl], + ["lab", d3.interpolateLab] +]); + +function maybeInterpolator(interpolate) { + const i = `${interpolate}`.toLowerCase(); + if (!interpolators.has(i)) throw new Error(`unknown interpolator: ${i}`); + return interpolators.get(i); +} + +function createScaleQ( + key, + scale, + channels, + { + type, + nice, + clamp, + zero, + domain = inferAutoDomain(key, channels), + unknown, + round, + scheme, + interval, + range = registry.get(key) === radius + ? inferRadialRange(channels, domain) + : registry.get(key) === length + ? inferLengthRange(channels, domain) + : registry.get(key) === opacity + ? unit + : undefined, + interpolate = registry.get(key) === color + ? scheme == null && range !== undefined + ? d3.interpolateRgb + : quantitativeScheme(scheme !== undefined ? scheme : type === "cyclical" ? "rainbow" : "turbo") + : round + ? d3.interpolateRound + : d3.interpolateNumber, + reverse + } +) { + interval = maybeRangeInterval(interval, type); + if (type === "cyclical" || type === "sequential") type = "linear"; // shorthand for color schemes + if (typeof interpolate !== "function") interpolate = maybeInterpolator(interpolate); // named interpolator + reverse = !!reverse; + + // If an explicit range is specified, and it has a different length than the + // domain, then redistribute the range using a piecewise interpolator. + if (range !== undefined) { + const n = (domain = arrayify(domain)).length; + const m = (range = arrayify(range)).length; + if (n !== m) { + if (interpolate.length === 1) throw new Error("invalid piecewise interpolator"); // e.g., turbo + interpolate = d3.piecewise(interpolate, range); + range = undefined; + } + } + + // Disambiguate between a two-argument interpolator that is used in + // conjunction with the range, and a one-argument “fixed” interpolator on the + // [0, 1] interval as with the RdBu color scheme. + if (interpolate.length === 1) { + if (reverse) { + interpolate = flip(interpolate); + reverse = false; + } + if (range === undefined) { + range = Float64Array.from(domain, (_, i) => i / (domain.length - 1)); + if (range.length === 2) range = unit; // optimize common case of [0, 1] + } + scale.interpolate((range === unit ? constant : interpolatePiecewise)(interpolate)); + } else { + scale.interpolate(interpolate); + } + + // If a zero option is specified, we assume that the domain is numeric, and we + // want to ensure that the domain crosses zero. However, note that the domain + // may be reversed (descending) so we shouldn’t assume that the first value is + // smaller than the last; and also it’s possible that the domain has more than + // two values for a “poly” scale. And lastly be careful not to mutate input! + if (zero) { + const [min, max] = d3.extent(domain); + if (min > 0 || max < 0) { + domain = slice(domain); + if (orderof(domain) !== Math.sign(min)) domain[domain.length - 1] = 0; // [2, 1] or [-2, -1] + else domain[0] = 0; // [1, 2] or [-1, -2] + } + } + + if (reverse) domain = d3.reverse(domain); + scale.domain(domain).unknown(unknown); + if (nice) scale.nice(maybeNice(nice, type)), (domain = scale.domain()); + if (range !== undefined) scale.range(range); + if (clamp) scale.clamp(clamp); + return {type, domain, range, scale, interpolate, interval}; +} + +function maybeNice(nice, type) { + return nice === true ? undefined : typeof nice === "number" ? nice : maybeNiceInterval(nice, type); +} + +function createScaleLinear(key, channels, options) { + return createScaleQ(key, d3.scaleLinear(), channels, options); +} + +function createScaleSqrt(key, channels, options) { + return createScalePow(key, channels, {...options, exponent: 0.5}); +} + +function createScalePow(key, channels, {exponent = 1, ...options}) { + return createScaleQ(key, d3.scalePow().exponent(exponent), channels, {...options, type: "pow"}); +} + +function createScaleLog(key, channels, {base = 10, domain = inferLogDomain(channels), ...options}) { + return createScaleQ(key, d3.scaleLog().base(base), channels, {...options, domain}); +} + +function createScaleSymlog(key, channels, {constant = 1, ...options}) { + return createScaleQ(key, d3.scaleSymlog().constant(constant), channels, options); +} + +function createScaleQuantile( + key, + channels, + { + range, + quantiles = range === undefined ? 5 : (range = [...range]).length, // deprecated; use n instead + n = quantiles, + scheme = "rdylbu", + domain = inferQuantileDomain(channels), + unknown, + interpolate, + reverse + } +) { + if (range === undefined) { + range = + interpolate !== undefined + ? d3.quantize(interpolate, n) + : registry.get(key) === color + ? ordinalRange(scheme, n) + : undefined; + } + if (domain.length > 0) { + domain = d3.scaleQuantile(domain, range === undefined ? {length: n} : range).quantiles(); + } + return createScaleThreshold(key, channels, {domain, range, reverse, unknown}); +} + +function createScaleQuantize( + key, + channels, + { + range, + n = range === undefined ? 5 : (range = [...range]).length, + scheme = "rdylbu", + domain = inferAutoDomain(key, channels), + unknown, + interpolate, + reverse + } +) { + const [min, max] = d3.extent(domain); + let thresholds; + if (range === undefined) { + thresholds = d3.ticks(min, max, n); // approximate number of nice, round thresholds + if (thresholds[0] <= min) thresholds.splice(0, 1); // drop exact lower bound + if (thresholds[thresholds.length - 1] >= max) thresholds.pop(); // drop exact upper bound + n = thresholds.length + 1; + range = + interpolate !== undefined + ? d3.quantize(interpolate, n) + : registry.get(key) === color + ? ordinalRange(scheme, n) + : undefined; + } else { + thresholds = d3.quantize(d3.interpolateNumber(min, max), n + 1).slice(1, -1); // exactly n - 1 thresholds to match range + if (min instanceof Date) thresholds = thresholds.map((x) => new Date(x)); // preserve date types + } + if (orderof(arrayify(domain)) < 0) thresholds.reverse(); // preserve descending domain + return createScaleThreshold(key, channels, {domain: thresholds, range, reverse, unknown}); +} + +function createScaleThreshold( + key, + channels, + { + domain = [0], // explicit thresholds in ascending order + unknown, + scheme = "rdylbu", + interpolate, + range = interpolate !== undefined + ? d3.quantize(interpolate, domain.length + 1) + : registry.get(key) === color + ? ordinalRange(scheme, domain.length + 1) + : undefined, + reverse + } +) { + domain = arrayify(domain); + const sign = orderof(domain); // preserve descending domain + if (!isNaN(sign) && !isOrdered(domain, sign)) throw new Error(`the ${key} scale has a non-monotonic domain`); + if (reverse) range = d3.reverse(range); // domain ascending, so reverse range + return { + type: "threshold", + scale: d3.scaleThreshold(sign < 0 ? d3.reverse(domain) : domain, range === undefined ? [] : range).unknown(unknown), + domain, + range + }; +} + +function isOrdered(domain, sign) { + for (let i = 1, n = domain.length, d = domain[0]; i < n; ++i) { + const s = d3.descending(d, (d = domain[i])); + if (s !== 0 && s !== sign) return false; + } + return true; +} + +// For non-numeric identity scales such as color and symbol, we can’t use D3’s +// identity scale because it coerces to number; and we can’t compute the domain +// (and equivalently range) since we can’t know whether the values are +// continuous or discrete. +function createScaleIdentity(key) { + return {type: "identity", scale: hasNumericRange(registry.get(key)) ? d3.scaleIdentity() : (d) => d}; +} + +function inferDomain$1(channels, f = finite$1) { + return channels.length + ? [ + d3.min(channels, ({value}) => (value === undefined ? value : d3.min(value, f))), + d3.max(channels, ({value}) => (value === undefined ? value : d3.max(value, f))) + ] + : [0, 1]; +} + +function inferAutoDomain(key, channels) { + const type = registry.get(key); + return (type === radius || type === opacity || type === length ? inferZeroDomain : inferDomain$1)(channels); +} + +function inferZeroDomain(channels) { + return [0, channels.length ? d3.max(channels, ({value}) => (value === undefined ? value : d3.max(value, finite$1))) : 1]; +} + +// We don’t want the upper bound of the radial domain to be zero, as this would +// be degenerate, so we ignore nonpositive values. We also don’t want the +// maximum default radius to exceed 30px. +function inferRadialRange(channels, domain) { + const hint = channels.find(({radius}) => radius !== undefined); + if (hint !== undefined) return [0, hint.radius]; // a natural maximum radius, e.g. hexbins + const h25 = d3.quantile(channels, 0.5, ({value}) => (value === undefined ? NaN : d3.quantile(value, 0.25, positive))); + const range = domain.map((d) => 3 * Math.sqrt(d / h25)); + const k = 30 / d3.max(range); + return k < 1 ? range.map((r) => r * k) : range; +} + +// We want a length scale’s domain to go from zero to a positive value, and to +// treat negative lengths if any as inverted vectors of equivalent magnitude. We +// also don’t want the maximum default length to exceed 60px. +function inferLengthRange(channels, domain) { + const h50 = d3.median(channels, ({value}) => (value === undefined ? NaN : d3.median(value, Math.abs))); + const range = domain.map((d) => (12 * d) / h50); + const k = 60 / d3.max(range); + return k < 1 ? range.map((r) => r * k) : range; +} + +function inferLogDomain(channels) { + for (const {value} of channels) { + if (value !== undefined) { + for (let v of value) { + if (v > 0) return inferDomain$1(channels, positive); + if (v < 0) return inferDomain$1(channels, negative); + } + } + } + return [1, 10]; +} + +function inferQuantileDomain(channels) { + const domain = []; + for (const {value} of channels) { + if (value === undefined) continue; + for (const v of value) domain.push(v); + } + return domain; +} + +function interpolatePiecewise(interpolate) { + return (i, j) => (t) => interpolate(i + t * (j - i)); +} + +function createScaleD( + key, + scale, + transform, + channels, + { + type, + nice, + clamp, + domain = inferDomain$1(channels), + unknown, + pivot = 0, + scheme, + range, + symmetric = true, + interpolate = registry.get(key) === color + ? scheme == null && range !== undefined + ? d3.interpolateRgb + : quantitativeScheme(scheme !== undefined ? scheme : "rdbu") + : d3.interpolateNumber, + reverse + } +) { + pivot = +pivot; + domain = arrayify(domain); + let [min, max] = domain; + if (domain.length > 2) warn(`Warning: the diverging ${key} scale domain contains extra elements.`); + + if (d3.descending(min, max) < 0) ([min, max] = [max, min]), (reverse = !reverse); + min = Math.min(min, pivot); + max = Math.max(max, pivot); + + // Sometimes interpolate is a named interpolator, such as "lab" for Lab color + // space. Other times interpolate is a function that takes two arguments and + // is used in conjunction with the range. And other times the interpolate + // function is a “fixed” interpolator on the [0, 1] interval, as when a + // color scheme such as interpolateRdBu is used. + if (typeof interpolate !== "function") { + interpolate = maybeInterpolator(interpolate); + } + + // If an explicit range is specified, promote it to a piecewise interpolator. + if (range !== undefined) { + interpolate = + interpolate.length === 1 ? interpolatePiecewise(interpolate)(...range) : d3.piecewise(interpolate, range); + } + + // Reverse before normalization. + if (reverse) interpolate = flip(interpolate); + + // Normalize the interpolator for symmetric difference around the pivot. + if (symmetric) { + const mid = transform.apply(pivot); + const mindelta = mid - transform.apply(min); + const maxdelta = transform.apply(max) - mid; + if (mindelta < maxdelta) min = transform.invert(mid - maxdelta); + else if (mindelta > maxdelta) max = transform.invert(mid + mindelta); + } + + scale.domain([min, pivot, max]).unknown(unknown).interpolator(interpolate); + if (clamp) scale.clamp(clamp); + if (nice) scale.nice(nice); + return {type, domain: [min, max], pivot, interpolate, scale}; +} + +function createScaleDiverging(key, channels, options) { + return createScaleD(key, d3.scaleDiverging(), transformIdentity, channels, options); +} + +function createScaleDivergingSqrt(key, channels, options) { + return createScaleDivergingPow(key, channels, {...options, exponent: 0.5}); +} + +function createScaleDivergingPow(key, channels, {exponent = 1, ...options}) { + return createScaleD(key, d3.scaleDivergingPow().exponent((exponent = +exponent)), transformPow(exponent), channels, { + ...options, + type: "diverging-pow" + }); +} + +function createScaleDivergingLog( + key, + channels, + {base = 10, pivot = 1, domain = inferDomain$1(channels, pivot < 0 ? negative : positive), ...options} +) { + return createScaleD(key, d3.scaleDivergingLog().base((base = +base)), transformLog, channels, { + domain, + pivot, + ...options + }); +} + +function createScaleDivergingSymlog(key, channels, {constant = 1, ...options}) { + return createScaleD( + key, + d3.scaleDivergingSymlog().constant((constant = +constant)), + transformSymlog(constant), + channels, + options + ); +} + +const transformIdentity = { + apply(x) { + return x; + }, + invert(x) { + return x; + } +}; + +const transformLog = { + apply: Math.log, + invert: Math.exp +}; + +const transformSqrt = { + apply(x) { + return Math.sign(x) * Math.sqrt(Math.abs(x)); + }, + invert(x) { + return Math.sign(x) * (x * x); + } +}; + +function transformPow(exponent) { + return exponent === 0.5 + ? transformSqrt + : { + apply(x) { + return Math.sign(x) * Math.pow(Math.abs(x), exponent); + }, + invert(x) { + return Math.sign(x) * Math.pow(Math.abs(x), 1 / exponent); + } + }; +} + +function transformSymlog(constant) { + return { + apply(x) { + return Math.sign(x) * Math.log1p(Math.abs(x / constant)); + }, + invert(x) { + return Math.sign(x) * Math.expm1(Math.abs(x)) * constant; + } + }; +} + +function createScaleT(key, scale, channels, options) { + return createScaleQ(key, scale, channels, options); +} + +function createScaleTime(key, channels, options) { + return createScaleT(key, d3.scaleTime(), channels, options); +} + +function createScaleUtc(key, channels, options) { + return createScaleT(key, d3.scaleUtc(), channels, options); +} + +// This denotes an implicitly ordinal color scale: the scale type was not set, +// but the associated values are strings or booleans. If the associated defined +// values are entirely boolean, the range will default to greys. You can opt out +// of this by setting the type explicitly. +const ordinalImplicit = Symbol("ordinal"); + +function createScaleO(key, scale, channels, {type, interval, domain, range, reverse, hint}) { + interval = maybeRangeInterval(interval, type); + if (domain === undefined) domain = inferDomain(channels, interval, key); + if (type === "categorical" || type === ordinalImplicit) type = "ordinal"; // shorthand for color schemes + if (reverse) domain = d3.reverse(domain); + domain = scale.domain(domain).domain(); // deduplicate + if (range !== undefined) { + // If the range is specified as a function, pass it the domain. + if (typeof range === "function") range = range(domain); + scale.range(range); + } + return {type, domain, range, scale, hint, interval}; +} + +function createScaleOrdinal(key, channels, {type, interval, domain, range, scheme, unknown, ...options}) { + interval = maybeRangeInterval(interval, type); + if (domain === undefined) domain = inferDomain(channels, interval, key); + let hint; + if (registry.get(key) === symbol) { + hint = inferSymbolHint(channels); + range = range === undefined ? inferSymbolRange(hint) : map$1(range, maybeSymbol); + } else if (registry.get(key) === color) { + if (range === undefined && (type === "ordinal" || type === ordinalImplicit)) { + range = maybeBooleanRange(domain, scheme); + if (range !== undefined) scheme = undefined; // Don’t re-apply scheme. + } + if (scheme === undefined && range === undefined) { + scheme = type === "ordinal" ? "turbo" : "tableau10"; + } + if (scheme !== undefined) { + if (range !== undefined) { + const interpolate = quantitativeScheme(scheme); + const t0 = range[0], + d = range[1] - range[0]; + range = ({length: n}) => d3.quantize((t) => interpolate(t0 + d * t), n); + } else { + range = ordinalScheme(scheme); + } + } + } + if (unknown === d3.scaleImplicit) { + throw new Error(`implicit unknown on ${key} scale is not supported`); + } + return createScaleO(key, d3.scaleOrdinal().unknown(unknown), channels, {...options, type, domain, range, hint}); +} + +function createScalePoint(key, channels, {align = 0.5, padding = 0.5, ...options}) { + return maybeRound(d3.scalePoint().align(align).padding(padding), channels, options, key); +} + +function createScaleBand( + key, + channels, + { + align = 0.5, + padding = 0.1, + paddingInner = padding, + paddingOuter = key === "fx" || key === "fy" ? 0 : padding, + ...options + } +) { + return maybeRound( + d3.scaleBand().align(align).paddingInner(paddingInner).paddingOuter(paddingOuter), + channels, + options, + key + ); +} + +function maybeRound(scale, channels, options, key) { + let {round} = options; + if (round !== undefined) scale.round((round = !!round)); + scale = createScaleO(key, scale, channels, options); + scale.round = round; // preserve for autoScaleRound + return scale; +} + +function inferDomain(channels, interval, key) { + const values = new d3.InternSet(); + for (const {value, domain} of channels) { + if (domain !== undefined) return domain(); // see channelDomain + if (value === undefined) continue; + for (const v of value) values.add(v); + } + if (interval !== undefined) { + const [min, max] = d3.extent(values).map(interval.floor, interval); + return interval.range(min, interval.offset(max)); + } + if (values.size > 10e3 && registry.get(key) === position$1) { + throw new Error(`implicit ordinal domain of ${key} scale has more than 10,000 values`); + } + return d3.sort(values, ascendingDefined); +} + +// If all channels provide a consistent hint, propagate it to the scale. +function inferHint(channels, key) { + let value; + for (const {hint} of channels) { + const candidate = hint?.[key]; + if (candidate === undefined) continue; // no hint here + if (value === undefined) value = candidate; // first hint + else if (value !== candidate) return; // inconsistent hint + } + return value; +} + +function inferSymbolHint(channels) { + return { + fill: inferHint(channels, "fill"), + stroke: inferHint(channels, "stroke") + }; +} + +function inferSymbolRange(hint) { + return isNoneish(hint.fill) ? d3.symbolsStroke : d3.symbolsFill; +} + +function createScales( + channelsByScale, + { + label: globalLabel, + inset: globalInset = 0, + insetTop: globalInsetTop = globalInset, + insetRight: globalInsetRight = globalInset, + insetBottom: globalInsetBottom = globalInset, + insetLeft: globalInsetLeft = globalInset, + round, + nice, + clamp, + zero, + align, + padding, + projection, + facet: {label: facetLabel = globalLabel} = {}, + ...options + } = {} +) { + const scales = {}; + for (const [key, channels] of channelsByScale) { + const scaleOptions = options[key]; + const scale = createScale(key, channels, { + round: registry.get(key) === position$1 ? round : undefined, // only for position + nice, + clamp, + zero, + align, + padding, + projection, + ...scaleOptions + }); + if (scale) { + // populate generic scale options (percent, transform, insets) + let { + label = key === "fx" || key === "fy" ? facetLabel : globalLabel, + percent, + transform, + inset, + insetTop = inset !== undefined ? inset : key === "y" ? globalInsetTop : 0, // not fy + insetRight = inset !== undefined ? inset : key === "x" ? globalInsetRight : 0, // not fx + insetBottom = inset !== undefined ? inset : key === "y" ? globalInsetBottom : 0, // not fy + insetLeft = inset !== undefined ? inset : key === "x" ? globalInsetLeft : 0 // not fx + } = scaleOptions || {}; + if (transform == null) transform = undefined; + else if (typeof transform !== "function") throw new Error("invalid scale transform; not a function"); + scale.percent = !!percent; + scale.label = label === undefined ? inferScaleLabel(channels, scale) : label; + scale.transform = transform; + if (key === "x" || key === "fx") { + scale.insetLeft = +insetLeft; + scale.insetRight = +insetRight; + } else if (key === "y" || key === "fy") { + scale.insetTop = +insetTop; + scale.insetBottom = +insetBottom; + } + scales[key] = scale; + } + } + return scales; +} + +function createScaleFunctions(descriptors) { + const scales = {}; + const scaleFunctions = {scales}; + for (const [key, descriptor] of Object.entries(descriptors)) { + const {scale, type, interval, label} = descriptor; + scales[key] = exposeScale(descriptor); + scaleFunctions[key] = scale; + // TODO: pass these properties, which are needed for axes, in the descriptor. + scale.type = type; + if (interval != null) scale.interval = interval; + if (label != null) scale.label = label; + } + return scaleFunctions; +} + +// Mutates scale.range! +function autoScaleRange(scales, dimensions) { + const {x, y, fx, fy} = scales; + const superdimensions = fx || fy ? outerDimensions(dimensions) : dimensions; + if (fx) autoScaleRangeX(fx, superdimensions); + if (fy) autoScaleRangeY(fy, superdimensions); + const subdimensions = fx || fy ? innerDimensions(scales, dimensions) : dimensions; + if (x) autoScaleRangeX(x, subdimensions); + if (y) autoScaleRangeY(y, subdimensions); +} + +// Channels can have labels; if all the channels for a given scale are +// consistently labeled (i.e., have the same value if not undefined), and the +// corresponding scale doesn’t already have an explicit label, then the +// channels’ label is promoted to the scale. This inferred label should have an +// orientation-appropriate arrow added when used as an axis, but we don’t want +// to add the arrow when the label is set explicitly as an option; so, the +// inferred label is distinguished as an object with an “inferred” property. +function inferScaleLabel(channels = [], scale) { + let label; + for (const {label: l} of channels) { + if (l === undefined) continue; + if (label === undefined) label = l; + else if (label !== l) return; + } + if (label === undefined) return; + if (!isOrdinalScale(scale) && scale.percent) label = `${label} (%)`; + return {inferred: true, toString: () => label}; +} + +// Determines whether the scale points in the “positive” (right or down) or +// “negative” (left or up) direction; if the scale order cannot be determined, +// returns NaN; used to assign an appropriate label arrow. +function inferScaleOrder(scale) { + return Math.sign(orderof(scale.domain())) * Math.sign(orderof(scale.range())); +} + +// Returns the dimensions of the outer frame; this is subdivided into facets +// with the margins of each facet collapsing into the outer margins. +function outerDimensions(dimensions) { + const { + marginTop, + marginRight, + marginBottom, + marginLeft, + width, + height, + facet: { + marginTop: facetMarginTop, + marginRight: facetMarginRight, + marginBottom: facetMarginBottom, + marginLeft: facetMarginLeft + } + } = dimensions; + return { + marginTop: Math.max(marginTop, facetMarginTop), + marginRight: Math.max(marginRight, facetMarginRight), + marginBottom: Math.max(marginBottom, facetMarginBottom), + marginLeft: Math.max(marginLeft, facetMarginLeft), + width, + height + }; +} + +// Returns the dimensions of each facet. +function innerDimensions({fx, fy}, dimensions) { + const {marginTop, marginRight, marginBottom, marginLeft, width, height} = outerDimensions(dimensions); + return { + marginTop, + marginRight, + marginBottom, + marginLeft, + width: fx ? fx.scale.bandwidth() + marginLeft + marginRight : width, + height: fy ? fy.scale.bandwidth() + marginTop + marginBottom : height, + facet: {width, height} + }; +} + +function autoScaleRangeX(scale, dimensions) { + if (scale.range === undefined) { + const {insetLeft, insetRight} = scale; + const {width, marginLeft = 0, marginRight = 0} = dimensions; + const left = marginLeft + insetLeft; + const right = width - marginRight - insetRight; + scale.range = [left, Math.max(left, right)]; + if (!isOrdinalScale(scale)) scale.range = piecewiseRange(scale); + scale.scale.range(scale.range); + } + autoScaleRound(scale); +} + +function autoScaleRangeY(scale, dimensions) { + if (scale.range === undefined) { + const {insetTop, insetBottom} = scale; + const {height, marginTop = 0, marginBottom = 0} = dimensions; + const top = marginTop + insetTop; + const bottom = height - marginBottom - insetBottom; + scale.range = [Math.max(top, bottom), top]; + if (!isOrdinalScale(scale)) scale.range = piecewiseRange(scale); + else scale.range.reverse(); + scale.scale.range(scale.range); + } + autoScaleRound(scale); +} + +function autoScaleRound(scale) { + if (scale.round === undefined && isBandScale(scale) && roundError(scale) <= 30) { + scale.scale.round(true); + } +} + +// If we were to turn on rounding for this band or point scale, how much wasted +// space would it introduce (on both ends of the range)? This must match +// d3.scaleBand’s rounding behavior: +// https://github.com/d3/d3-scale/blob/83555bd759c7314420bd4240642beda5e258db9e/src/band.js#L20-L32 +function roundError({scale}) { + const n = scale.domain().length; + const [start, stop] = scale.range(); + const paddingInner = scale.paddingInner ? scale.paddingInner() : 1; + const paddingOuter = scale.paddingOuter ? scale.paddingOuter() : scale.padding(); + const m = n - paddingInner; + const step = Math.abs(stop - start) / Math.max(1, m + paddingOuter * 2); + return (step - Math.floor(step)) * m; +} + +function piecewiseRange(scale) { + const length = scale.scale.domain().length + isThresholdScale(scale); + if (!(length > 2)) return scale.range; + const [start, end] = scale.range; + return Array.from({length}, (_, i) => start + (i / (length - 1)) * (end - start)); +} + +function normalizeScale(key, scale, hint) { + return createScale(key, hint === undefined ? undefined : [{hint}], {...scale}); +} + +function createScale(key, channels = [], options = {}) { + const type = inferScaleType(key, channels, options); + + // Warn for common misuses of implicit ordinal scales. We disable this test if + // you specify a scale interval or if you set the domain or range explicitly, + // since setting the domain or range (typically with a cardinality of more than + // two) is another indication that you intended for the scale to be ordinal; we + // also disable it for facet scales since these are always band scales. + if ( + options.type === undefined && + options.domain === undefined && + options.range === undefined && + options.interval == null && + key !== "fx" && + key !== "fy" && + isOrdinalScale({type}) + ) { + const values = channels.map(({value}) => value).filter((value) => value !== undefined); + if (values.some(isTemporal)) + warn( + `Warning: some data associated with the ${key} scale are dates. Dates are typically associated with a "utc" or "time" scale rather than a "${formatScaleType( + type + )}" scale. If you are using a bar mark, you probably want a rect mark with the interval option instead; if you are using a group transform, you probably want a bin transform instead. If you want to treat this data as ordinal, you can specify the interval of the ${key} scale (e.g., d3.utcDay), or you can suppress this warning by setting the type of the ${key} scale to "${formatScaleType( + type + )}".` + ); + else if (values.some(isTemporalString)) + warn( + `Warning: some data associated with the ${key} scale are strings that appear to be dates (e.g., YYYY-MM-DD). If these strings represent dates, you should parse them to Date objects. Dates are typically associated with a "utc" or "time" scale rather than a "${formatScaleType( + type + )}" scale. If you are using a bar mark, you probably want a rect mark with the interval option instead; if you are using a group transform, you probably want a bin transform instead. If you want to treat this data as ordinal, you can suppress this warning by setting the type of the ${key} scale to "${formatScaleType( + type + )}".` + ); + else if (values.some(isNumericString)) + warn( + `Warning: some data associated with the ${key} scale are strings that appear to be numbers. If these strings represent numbers, you should parse or coerce them to numbers. Numbers are typically associated with a "linear" scale rather than a "${formatScaleType( + type + )}" scale. If you want to treat this data as ordinal, you can specify the interval of the ${key} scale (e.g., 1 for integers), or you can suppress this warning by setting the type of the ${key} scale to "${formatScaleType( + type + )}".` + ); + } + + options.type = type; // Mutates input! + + // Once the scale type is known, coerce the associated channel values and any + // explicitly-specified domain to the expected type. + switch (type) { + case "diverging": + case "diverging-sqrt": + case "diverging-pow": + case "diverging-log": + case "diverging-symlog": + case "cyclical": + case "sequential": + case "linear": + case "sqrt": + case "threshold": + case "quantile": + case "pow": + case "log": + case "symlog": + options = coerceType(channels, options, coerceNumbers); + break; + case "identity": + switch (registry.get(key)) { + case position$1: + options = coerceType(channels, options, coerceNumbers); + break; + case symbol: + options = coerceType(channels, options, coerceSymbols); + break; + } + break; + case "utc": + case "time": + options = coerceType(channels, options, coerceDates); + break; + } + + switch (type) { + case "diverging": + return createScaleDiverging(key, channels, options); + case "diverging-sqrt": + return createScaleDivergingSqrt(key, channels, options); + case "diverging-pow": + return createScaleDivergingPow(key, channels, options); + case "diverging-log": + return createScaleDivergingLog(key, channels, options); + case "diverging-symlog": + return createScaleDivergingSymlog(key, channels, options); + case "categorical": + case "ordinal": + case ordinalImplicit: + return createScaleOrdinal(key, channels, options); + case "cyclical": + case "sequential": + case "linear": + return createScaleLinear(key, channels, options); + case "sqrt": + return createScaleSqrt(key, channels, options); + case "threshold": + return createScaleThreshold(key, channels, options); + case "quantile": + return createScaleQuantile(key, channels, options); + case "quantize": + return createScaleQuantize(key, channels, options); + case "pow": + return createScalePow(key, channels, options); + case "log": + return createScaleLog(key, channels, options); + case "symlog": + return createScaleSymlog(key, channels, options); + case "utc": + return createScaleUtc(key, channels, options); + case "time": + return createScaleTime(key, channels, options); + case "point": + return createScalePoint(key, channels, options); + case "band": + return createScaleBand(key, channels, options); + case "identity": + return createScaleIdentity(key); + case undefined: + return; + default: + throw new Error(`unknown scale type: ${type}`); + } +} + +function formatScaleType(type) { + return typeof type === "symbol" ? type.description : type; +} + +function maybeScaleType(type) { + return typeof type === "string" ? `${type}`.toLowerCase() : type; +} + +// A special type symbol when the x and y scales are replaced with a projection. +const typeProjection = {toString: () => "projection"}; + +function inferScaleType(key, channels, {type, domain, range, scheme, pivot, projection}) { + type = maybeScaleType(type); + + // The facet scales are always band scales; this cannot be changed. + if (key === "fx" || key === "fy") return "band"; + + // If a projection is specified, the x- and y-scales are disabled; these + // channels will be projected rather than scaled. (But still check that none + // of the associated channels are incompatible with a projection.) + if ((key === "x" || key === "y") && projection != null) type = typeProjection; + + // If a channel dictates a scale type, make sure that it is consistent with + // the user-specified scale type (if any) and all other channels. For example, + // barY requires x to be a band scale and disallows any other scale type. + for (const channel of channels) { + const t = maybeScaleType(channel.type); + if (t === undefined) continue; + else if (type === undefined) type = t; + else if (type !== t) throw new Error(`scale incompatible with channel: ${type} !== ${t}`); + } + + // If the scale, a channel, or user specified a (consistent) type, return it. + if (type === typeProjection) return; + if (type !== undefined) return type; + + // If there’s no data (and no type) associated with this scale, don’t create a scale. + if (domain === undefined && !channels.some(({value}) => value !== undefined)) return; + + // Some scales have default types. + const kind = registry.get(key); + if (kind === radius) return "sqrt"; + if (kind === opacity || kind === length) return "linear"; + if (kind === symbol) return "ordinal"; + + // If the domain or range has more than two values, assume it’s ordinal. You + // can still use a “piecewise” (or “polylinear”) scale, but you must set the + // type explicitly. + if ((domain || range || []).length > 2) return asOrdinalType(kind); + + // Otherwise, infer the scale type from the data! Prefer the domain, if + // present, over channels. (The domain and channels should be consistently + // typed, and the domain is more explicit and typically much smaller.) We only + // check the first defined value for expedience and simplicity; we expect + // that the types are consistent. + if (domain !== undefined) { + if (isOrdinal(domain)) return asOrdinalType(kind); + if (isTemporal(domain)) return "utc"; + } else { + const values = channels.map(({value}) => value).filter((value) => value !== undefined); + if (values.some(isOrdinal)) return asOrdinalType(kind); + if (values.some(isTemporal)) return "utc"; + } + + // For color scales, take a hint from the color scheme and pivot option. + if (kind === color) { + if (pivot != null || isDivergingScheme(scheme)) return "diverging"; + if (isCategoricalScheme(scheme)) return "categorical"; + } + + return "linear"; +} + +// Positional scales default to a point scale instead of an ordinal scale. +function asOrdinalType(kind) { + switch (kind) { + case position$1: + return "point"; + case color: + return ordinalImplicit; + default: + return "ordinal"; + } +} + +function isOrdinalScale({type}) { + return type === "ordinal" || type === "point" || type === "band" || type === ordinalImplicit; +} + +function isThresholdScale({type}) { + return type === "threshold"; +} + +function isBandScale({type}) { + return type === "point" || type === "band"; +} + +// Certain marks have special behavior if a scale is collapsed, i.e. if the +// domain is degenerate and represents only a single value such as [3, 3]; for +// example, a rect will span the full extent of the chart along a collapsed +// dimension (whereas a dot will simply be drawn in the center). +function isCollapsed(scale) { + if (scale === undefined) return true; // treat missing scale as collapsed + const domain = scale.domain(); + const value = scale(domain[0]); + for (let i = 1, n = domain.length; i < n; ++i) { + if (scale(domain[i]) - value) { + return false; + } + } + return true; +} + +// Mutates channel.value! +function coerceType(channels, {domain, ...options}, coerceValues) { + for (const c of channels) { + if (c.value !== undefined) { + if (domain === undefined) domain = c.value?.domain; // promote channel domain + c.value = coerceValues(c.value); + } + } + return { + domain: domain === undefined ? domain : coerceValues(domain), + ...options + }; +} + +function coerceSymbols(values) { + return map$1(values, maybeSymbol); +} + +function scale(options = {}) { + let scale; + for (const key in options) { + if (!registry.has(key)) continue; // ignore unknown properties + if (!isScaleOptions(options[key])) continue; // e.g., ignore {color: "red"} + if (scale !== undefined) throw new Error("ambiguous scale definition; multiple scales found"); + scale = exposeScale(normalizeScale(key, options[key])); + } + if (scale === undefined) throw new Error("invalid scale definition; no scale found"); + return scale; +} + +function exposeScales(scales) { + return (key) => { + if (!registry.has((key = `${key}`))) throw new Error(`unknown scale: ${key}`); + return scales[key]; + }; +} + +// Note: axis- and legend-related properties (such as label, ticks and +// tickFormat) are not included here as they do not affect the scale’s behavior. +function exposeScale({scale, type, domain, range, interpolate, interval, transform, percent, pivot}) { + if (type === "identity") return {type: "identity", apply: (d) => d, invert: (d) => d}; + const unknown = scale.unknown ? scale.unknown() : undefined; + return { + type, + domain: slice(domain), // defensive copy + ...(range !== undefined && {range: slice(range)}), // defensive copy + ...(transform !== undefined && {transform}), + ...(percent && {percent}), // only exposed if truthy + ...(unknown !== undefined && {unknown}), + ...(interval !== undefined && {interval}), + + // quantitative + ...(interpolate !== undefined && {interpolate}), + ...(scale.clamp && {clamp: scale.clamp()}), + + // diverging (always asymmetric; we never want to apply the symmetric transform twice) + ...(pivot !== undefined && {pivot, symmetric: false}), + + // log, diverging-log + ...(scale.base && {base: scale.base()}), + + // pow, diverging-pow + ...(scale.exponent && {exponent: scale.exponent()}), + + // symlog, diverging-symlog + ...(scale.constant && {constant: scale.constant()}), + + // band, point + ...(scale.align && {align: scale.align(), round: scale.round()}), + ...(scale.padding && + (scale.paddingInner + ? {paddingInner: scale.paddingInner(), paddingOuter: scale.paddingOuter()} + : {padding: scale.padding()})), + ...(scale.bandwidth && {bandwidth: scale.bandwidth(), step: scale.step()}), + + // utilities + apply: (t) => scale(t), + ...(scale.invert && {invert: (t) => scale.invert(t)}) + }; +} + +function createDimensions(scales, marks, options = {}) { + // Compute the default margins: the maximum of the marks’ margins. While not + // always used, they may be needed to compute the default height of the plot. + let marginTopDefault = 0.5 - offset, + marginRightDefault = 0.5 + offset, + marginBottomDefault = 0.5 + offset, + marginLeftDefault = 0.5 - offset; + + for (const {marginTop, marginRight, marginBottom, marginLeft} of marks) { + if (marginTop > marginTopDefault) marginTopDefault = marginTop; + if (marginRight > marginRightDefault) marginRightDefault = marginRight; + if (marginBottom > marginBottomDefault) marginBottomDefault = marginBottom; + if (marginLeft > marginLeftDefault) marginLeftDefault = marginLeft; + } + + // Compute the actual margins. The order of precedence is: the side-specific + // margin options, then the global margin option, then the defaults. + let { + margin, + marginTop = margin !== undefined ? margin : marginTopDefault, + marginRight = margin !== undefined ? margin : marginRightDefault, + marginBottom = margin !== undefined ? margin : marginBottomDefault, + marginLeft = margin !== undefined ? margin : marginLeftDefault + } = options; + + // Coerce the margin options to numbers. + marginTop = +marginTop; + marginRight = +marginRight; + marginBottom = +marginBottom; + marginLeft = +marginLeft; + + // Compute the outer dimensions of the plot. If the top and bottom margins are + // specified explicitly, adjust the automatic height accordingly. + let { + width = 640, + height = autoHeight(scales, options, { + width, + marginTopDefault, + marginRightDefault, + marginBottomDefault, + marginLeftDefault + }) + Math.max(0, marginTop - marginTopDefault + marginBottom - marginBottomDefault) + } = options; + + // Coerce the width and height. + width = +width; + height = +height; + + const dimensions = { + width, + height, + marginTop, + marginRight, + marginBottom, + marginLeft + }; + + // Compute the facet margins. + if (scales.fx || scales.fy) { + let { + margin: facetMargin, + marginTop: facetMarginTop = facetMargin !== undefined ? facetMargin : marginTop, + marginRight: facetMarginRight = facetMargin !== undefined ? facetMargin : marginRight, + marginBottom: facetMarginBottom = facetMargin !== undefined ? facetMargin : marginBottom, + marginLeft: facetMarginLeft = facetMargin !== undefined ? facetMargin : marginLeft + } = options.facet ?? {}; + + // Coerce the facet margin options to numbers. + facetMarginTop = +facetMarginTop; + facetMarginRight = +facetMarginRight; + facetMarginBottom = +facetMarginBottom; + facetMarginLeft = +facetMarginLeft; + + dimensions.facet = { + marginTop: facetMarginTop, + marginRight: facetMarginRight, + marginBottom: facetMarginBottom, + marginLeft: facetMarginLeft + }; + } + + return dimensions; +} + +function autoHeight( + {x, y, fy, fx}, + {projection, aspectRatio}, + {width, marginTopDefault, marginRightDefault, marginBottomDefault, marginLeftDefault} +) { + const nfy = fy ? fy.scale.domain().length : 1; + + // If a projection is specified, use its natural aspect ratio (if known). + const ar = projectionAspectRatio(projection); + if (ar) { + const nfx = fx ? fx.scale.domain().length : 1; + const far = ((1.1 * nfy - 0.1) / (1.1 * nfx - 0.1)) * ar; // 0.1 is default facet padding + const lar = Math.max(0.1, Math.min(10, far)); // clamp the aspect ratio to a “reasonable” value + return Math.round((width - marginLeftDefault - marginRightDefault) * lar + marginTopDefault + marginBottomDefault); + } + + const ny = y ? (isOrdinalScale(y) ? y.scale.domain().length : Math.max(7, 17 / nfy)) : 1; + + // If a desired aspect ratio is given, compute a default height to match. + if (aspectRatio != null) { + aspectRatio = +aspectRatio; + if (!(isFinite(aspectRatio) && aspectRatio > 0)) throw new Error(`invalid aspectRatio: ${aspectRatio}`); + const ratio = aspectRatioLength("y", y) / (aspectRatioLength("x", x) * aspectRatio); + const fxb = fx ? fx.scale.bandwidth() : 1; + const fyb = fy ? fy.scale.bandwidth() : 1; + const w = fxb * (width - marginLeftDefault - marginRightDefault) - x.insetLeft - x.insetRight; + return (ratio * w + y.insetTop + y.insetBottom) / fyb + marginTopDefault + marginBottomDefault; + } + + return !!(y || fy) * Math.max(1, Math.min(60, ny * nfy)) * 20 + !!fx * 30 + 60; +} + +function aspectRatioLength(k, scale) { + if (!scale) throw new Error(`aspectRatio requires ${k} scale`); + const {type, domain} = scale; + let transform; + switch (type) { + case "linear": + case "utc": + case "time": + transform = Number; + break; + case "pow": { + const exponent = scale.scale.exponent(); + transform = (x) => Math.pow(x, exponent); + break; + } + case "log": + transform = Math.log; + break; + case "point": + case "band": + return domain.length; + default: + throw new Error(`unsupported ${k} scale for aspectRatio: ${type}`); + } + const [min, max] = d3.extent(domain); + return Math.abs(transform(max) - transform(min)); +} + +// Returns an array of {x?, y?, i} objects representing the facet domain. +function createFacets(channelsByScale, options) { + const {fx, fy} = createScales(channelsByScale, options); + const fxDomain = fx?.scale.domain(); + const fyDomain = fy?.scale.domain(); + return fxDomain && fyDomain + ? d3.cross(fxDomain, fyDomain).map(([x, y], i) => ({x, y, i})) + : fxDomain + ? fxDomain.map((x, i) => ({x, i})) + : fyDomain + ? fyDomain.map((y, i) => ({y, i})) + : undefined; +} + +function recreateFacets(facets, {x: X, y: Y}) { + X &&= facetIndex(X); + Y &&= facetIndex(Y); + return facets + .filter( + X && Y // remove any facets no longer present in the domain + ? (f) => X.has(f.x) && Y.has(f.y) + : X + ? (f) => X.has(f.x) + : (f) => Y.has(f.y) + ) + .sort( + X && Y // reorder facets to match the new scale domains + ? (a, b) => X.get(a.x) - X.get(b.x) || Y.get(a.y) - Y.get(b.y) + : X + ? (a, b) => X.get(a.x) - X.get(b.x) + : (a, b) => Y.get(a.y) - Y.get(b.y) + ); +} + +// Returns a (possibly nested) Map of [[key1, index1], [key2, index2], …] +// representing the data indexes associated with each facet. +function facetGroups(data, {fx, fy}) { + const I = range(data); + const FX = fx?.value; + const FY = fy?.value; + return fx && fy + ? d3.rollup( + I, + (G) => ((G.fx = FX[G[0]]), (G.fy = FY[G[0]]), G), + (i) => FX[i], + (i) => FY[i] + ) + : fx + ? d3.rollup( + I, + (G) => ((G.fx = FX[G[0]]), G), + (i) => FX[i] + ) + : d3.rollup( + I, + (G) => ((G.fy = FY[G[0]]), G), + (i) => FY[i] + ); +} + +function facetTranslator(fx, fy, {marginTop, marginLeft}) { + return fx && fy + ? ({x, y}) => `translate(${fx(x) - marginLeft},${fy(y) - marginTop})` + : fx + ? ({x}) => `translate(${fx(x) - marginLeft},0)` + : ({y}) => `translate(0,${fy(y) - marginTop})`; +} + +// Returns an index that for each facet lists all the elements present in other +// facets in the original index. TODO Memoize to avoid repeated work? +function facetExclude(index) { + const ex = []; + const e = new Uint32Array(d3.sum(index, (d) => d.length)); + for (const i of index) { + let n = 0; + for (const j of index) { + if (i === j) continue; + e.set(j, n); + n += j.length; + } + ex.push(e.slice(0, n)); + } + return ex; +} + +const facetAnchors = new Map([ + ["top", facetAnchorTop], + ["right", facetAnchorRight], + ["bottom", facetAnchorBottom], + ["left", facetAnchorLeft], + ["top-left", and(facetAnchorTop, facetAnchorLeft)], + ["top-right", and(facetAnchorTop, facetAnchorRight)], + ["bottom-left", and(facetAnchorBottom, facetAnchorLeft)], + ["bottom-right", and(facetAnchorBottom, facetAnchorRight)], + ["top-empty", facetAnchorTopEmpty], + ["right-empty", facetAnchorRightEmpty], + ["bottom-empty", facetAnchorBottomEmpty], + ["left-empty", facetAnchorLeftEmpty], + ["empty", facetAnchorEmpty] +]); + +function maybeFacetAnchor(facetAnchor) { + if (facetAnchor == null) return null; + const anchor = facetAnchors.get(`${facetAnchor}`.toLowerCase()); + if (anchor) return anchor; + throw new Error(`invalid facet anchor: ${facetAnchor}`); +} + +const indexCache = new WeakMap(); + +function facetIndex(V) { + let I = indexCache.get(V); + if (!I) indexCache.set(V, (I = new d3.InternMap(map$1(V, (v, i) => [v, i])))); + return I; +} + +// Like V.indexOf(v), but with the same semantics as InternMap. +function facetIndexOf(V, v) { + return facetIndex(V).get(v); +} + +// Like facets.find, but with the same semantics as InternMap. +function facetFind(facets, x, y) { + x = keyof(x); + y = keyof(y); + return facets.find((f) => Object.is(keyof(f.x), x) && Object.is(keyof(f.y), y)); +} + +function facetEmpty(facets, x, y) { + return facetFind(facets, x, y)?.empty; +} + +function facetAnchorTop(facets, {y: Y}, {y}) { + return Y ? facetIndexOf(Y, y) === 0 : true; +} + +function facetAnchorBottom(facets, {y: Y}, {y}) { + return Y ? facetIndexOf(Y, y) === Y.length - 1 : true; +} + +function facetAnchorLeft(facets, {x: X}, {x}) { + return X ? facetIndexOf(X, x) === 0 : true; +} + +function facetAnchorRight(facets, {x: X}, {x}) { + return X ? facetIndexOf(X, x) === X.length - 1 : true; +} + +function facetAnchorTopEmpty(facets, {y: Y}, {x, y, empty}) { + if (empty) return false; + if (!Y) return; + const i = facetIndexOf(Y, y); + if (i > 0) return facetEmpty(facets, x, Y[i - 1]); +} + +function facetAnchorBottomEmpty(facets, {y: Y}, {x, y, empty}) { + if (empty) return false; + if (!Y) return; + const i = facetIndexOf(Y, y); + if (i < Y.length - 1) return facetEmpty(facets, x, Y[i + 1]); +} + +function facetAnchorLeftEmpty(facets, {x: X}, {x, y, empty}) { + if (empty) return false; + if (!X) return; + const i = facetIndexOf(X, x); + if (i > 0) return facetEmpty(facets, X[i - 1], y); +} + +function facetAnchorRightEmpty(facets, {x: X}, {x, y, empty}) { + if (empty) return false; + if (!X) return; + const i = facetIndexOf(X, x); + if (i < X.length - 1) return facetEmpty(facets, X[i + 1], y); +} + +function facetAnchorEmpty(facets, channels, {empty}) { + return empty; +} + +function and(a, b) { + return function () { + return a.apply(null, arguments) && b.apply(null, arguments); + }; +} + +// Facet filter, by mark; for now only the "eq" filter is provided. +function facetFilter(facets, {channels: {fx, fy}, groups}) { + return fx && fy + ? facets.map(({x, y}) => groups.get(x)?.get(y) ?? []) + : fx + ? facets.map(({x}) => groups.get(x) ?? []) + : facets.map(({y}) => groups.get(y) ?? []); +} + +class Mark { + constructor(data, channels = {}, options = {}, defaults) { + const { + facet = "auto", + facetAnchor, + fx, + fy, + sort, + dx = 0, + dy = 0, + margin = 0, + marginTop = margin, + marginRight = margin, + marginBottom = margin, + marginLeft = margin, + clip = defaults?.clip, + channels: extraChannels, + tip, + render + } = options; + this.data = data; + this.sort = isDomainSort(sort) ? sort : null; + this.initializer = initializer(options).initializer; + this.transform = this.initializer ? options.transform : basic(options).transform; + if (facet === null || facet === false) { + this.facet = null; + } else { + this.facet = keyword(facet === true ? "include" : facet, "facet", ["auto", "include", "exclude", "super"]); + this.fx = data === singleton && typeof fx === "string" ? [fx] : fx; + this.fy = data === singleton && typeof fy === "string" ? [fy] : fy; + } + this.facetAnchor = maybeFacetAnchor(facetAnchor); + channels = maybeNamed(channels); + if (extraChannels !== undefined) channels = {...maybeChannels(extraChannels), ...channels}; + if (defaults !== undefined) channels = {...styles(this, options, defaults), ...channels}; + this.channels = Object.fromEntries( + Object.entries(channels) + .map(([name, channel]) => { + if (isOptions(channel.value)) { + // apply scale and label overrides + const {value, label = channel.label, scale = channel.scale} = channel.value; + channel = {...channel, label, scale, value}; + } + if (data === singleton && typeof channel.value === "string") { + // convert field names to singleton values for decoration marks (e.g., frame) + const {value} = channel; + channel = {...channel, value: [value]}; + } + return [name, channel]; + }) + .filter(([name, {value, optional}]) => { + if (value != null) return true; + if (optional) return false; + throw new Error(`missing channel value: ${name}`); + }) + ); + this.dx = +dx; + this.dy = +dy; + this.marginTop = +marginTop; + this.marginRight = +marginRight; + this.marginBottom = +marginBottom; + this.marginLeft = +marginLeft; + this.clip = maybeClip(clip); + this.tip = maybeTip(tip); + // Super-faceting currently disallow position channels; in the future, we + // could allow position to be specified in fx and fy in addition to (or + // instead of) x and y. + if (this.facet === "super") { + if (fx || fy) throw new Error(`super-faceting cannot use fx or fy`); + for (const name in this.channels) { + const {scale} = channels[name]; + if (scale !== "x" && scale !== "y") continue; + throw new Error(`super-faceting cannot use x or y`); + } + } + if (render != null) { + this.render = composeRender(render, this.render); + } + } + initialize(facets, facetChannels, plotOptions) { + let data = arrayify(this.data); + if (facets === undefined && data != null) facets = [range(data)]; + const originalFacets = facets; + if (this.transform != null) (({facets, data} = this.transform(data, facets, plotOptions))), (data = arrayify(data)); + if (facets !== undefined) facets.original = originalFacets; // needed to read facetChannels + const channels = createChannels(this.channels, data); + if (this.sort != null) channelDomain(data, facets, channels, facetChannels, this.sort); // mutates facetChannels! + return {data, facets, channels}; + } + filter(index, channels, values) { + for (const name in channels) { + const {filter = defined} = channels[name]; + if (filter !== null) { + const value = values[name]; + index = index.filter((i) => filter(value[i])); + } + } + return index; + } + // If there is a projection, and there are paired x and y channels associated + // with the x and y scale respectively (and not already in screen coordinates + // as with an initializer), then apply the projection, replacing the x and y + // values. Note that the x and y scales themselves don’t exist if there is a + // projection, but whether the channels are associated with scales still + // determines whether the projection should apply; think of the projection as + // a combination xy-scale. + project(channels, values, context) { + for (const cx in channels) { + if (channels[cx].scale === "x" && /^x|x$/.test(cx)) { + const cy = cx.replace(/^x|x$/, "y"); + if (cy in channels && channels[cy].scale === "y") { + project(cx, cy, values, context.projection); + } + } + } + } + scale(channels, scales, context) { + const values = valueObject(channels, scales); + if (context.projection) this.project(channels, values, context); + return values; + } +} + +function marks(...marks) { + marks.plot = Mark.prototype.plot; // Note: depends on side-effect in plot! + return marks; +} + +function composeRender(r1, r2) { + if (r1 == null) return r2 === null ? undefined : r2; + if (r2 == null) return r1 === null ? undefined : r1; + if (typeof r1 !== "function") throw new TypeError(`invalid render transform: ${r1}`); + if (typeof r2 !== "function") throw new TypeError(`invalid render transform: ${r2}`); + return function (i, s, v, d, c, next) { + return r1.call(this, i, s, v, d, c, (i, s, v, d, c) => { + return r2.call(this, i, s, v, d, c, next); // preserve this + }); + }; +} + +function maybeChannels(channels) { + return Object.fromEntries( + Object.entries(maybeNamed(channels)).map(([name, channel]) => { + channel = typeof channel === "string" ? {value: channel, label: name} : maybeValue(channel); // for shorthand extra channels, use name as label + if (channel.filter === undefined && channel.scale == null) channel = {...channel, filter: null}; + return [name, channel]; + }) + ); +} + +function maybeTip(tip) { + return tip === true + ? "xy" + : tip === false || tip == null + ? null + : typeof tip === "string" + ? keyword(tip, "tip", ["x", "y", "xy"]) + : tip; // tip options object +} + +function withTip(options, pointer) { + return options?.tip === true + ? {...options, tip: pointer} + : isObject(options?.tip) && options.tip.pointer === undefined + ? {...options, tip: {...options.tip, pointer}} + : options; +} + +const states = new WeakMap(); + +function pointerK(kx, ky, {x, y, px, py, maxRadius = 40, channels, render, ...options} = {}) { + maxRadius = +maxRadius; + // When px or py is used, register an extra channel that the pointer + // interaction can use to control which point is focused; this allows pointing + // to function independently of where the downstream mark (e.g., a tip) is + // displayed. Also default x or y to null to disable maybeTuple etc. + if (px != null) (x ??= null), (channels = {...channels, px: {value: px, scale: "x"}}); + if (py != null) (y ??= null), (channels = {...channels, py: {value: py, scale: "y"}}); + return { + x, + y, + channels, + ...options, + // Unlike other composed transforms, the render transform must be the + // outermost render function because it will re-render dynamically in + // response to pointer events. + render: composeRender(function (index, scales, values, dimensions, context, next) { + context = {...context, pointerSticky: false}; + const svg = context.ownerSVGElement; + const {data} = context.getMarkState(this); + + // Isolate state per-pointer, per-plot; if the pointer is reused by + // multiple marks, they will share the same state (e.g., sticky modality). + let state = states.get(svg); + if (!state) states.set(svg, (state = {sticky: false, roots: [], renders: []})); + + // This serves as a unique identifier of the rendered mark per-plot; it is + // used to record the currently-rendered elements (state.roots) so that we + // can tell when a rendered element is clicked on. + let renderIndex = state.renders.push(render) - 1; + + // For faceting, we want to compute the local coordinates of each point, + // which means subtracting out the facet translation, if any. (It’s + // tempting to do this using the local coordinates in SVG, but that’s + // complicated by mark-specific transforms such as dx and dy.) Also, since + // band scales return the upper bound of the band, we have to offset by + // half the bandwidth. + const {x, y, fx, fy} = scales; + let tx = fx ? fx(index.fx) - dimensions.marginLeft : 0; + let ty = fy ? fy(index.fy) - dimensions.marginTop : 0; + if (x?.bandwidth) tx += x.bandwidth() / 2; + if (y?.bandwidth) ty += y.bandwidth() / 2; + + // For faceting, we also need to record the closest point per facet per + // mark (!), since each facet has its own pointer event listeners; we only + // want the closest point across facets to be visible. + const faceted = index.fi != null; + let facetState; + if (faceted) { + let facetStates = state.facetStates; + if (!facetStates) state.facetStates = facetStates = new Map(); + facetState = facetStates.get(this); + if (!facetState) facetStates.set(this, (facetState = new Map())); + } + + // The order of precedence for the pointer position is: px & py; the + // middle of x1 & y1 and x2 & y2; or x1 & y1 (e.g., area); or lastly x & + // y. If a dimension is unspecified, the frame anchor is used. + const [cx, cy] = applyFrameAnchor(this, dimensions); + const {px: PX, py: PY} = values; + const px = PX ? (i) => PX[i] : anchorX$1(values, cx); + const py = PY ? (i) => PY[i] : anchorY$1(values, cy); + + let i; // currently focused index + let g; // currently rendered mark + let s; // currently rendered stickiness + let f; // current animation frame + + // When faceting, if more than one pointer would be visible, only show + // this one if it is the closest. We defer rendering using an animation + // frame to allow all pointer events to be received before deciding which + // mark to render; although when hiding, we render immediately. + function update(ii, ri) { + if (faceted) { + if (f) f = cancelAnimationFrame(f); + if (ii == null) facetState.delete(index.fi); + else { + facetState.set(index.fi, ri); + f = requestAnimationFrame(() => { + f = null; + for (const [fi, r] of facetState) { + if (r < ri || (r === ri && fi < index.fi)) { + ii = null; + break; + } + } + render(ii); + }); + return; + } + } + render(ii); + } + + function render(ii) { + if (i === ii && s === state.sticky) return; // the tooltip hasn’t moved + i = ii; + s = context.pointerSticky = state.sticky; + const I = i == null ? [] : [i]; + if (faceted) (I.fx = index.fx), (I.fy = index.fy), (I.fi = index.fi); + const r = next(I, scales, values, dimensions, context); + if (g) { + // When faceting, preserve swapped mark and facet transforms; also + // remove ARIA attributes since these are promoted to the parent. This + // is perhaps brittle in that it depends on how Plot renders facets, + // but it produces a cleaner and more accessible SVG structure. + if (faceted) { + const p = g.parentNode; + const ft = g.getAttribute("transform"); + const mt = r.getAttribute("transform"); + ft ? r.setAttribute("transform", ft) : r.removeAttribute("transform"); + mt ? p.setAttribute("transform", mt) : p.removeAttribute("transform"); + r.removeAttribute("aria-label"); + r.removeAttribute("aria-description"); + r.removeAttribute("aria-hidden"); + } + g.replaceWith(r); + } + state.roots[renderIndex] = g = r; + + // Dispatch the value. When simultaneously exiting this facet and + // entering a new one, prioritize the entering facet. + if (!(i == null && facetState?.size > 1)) context.dispatchValue(i == null ? null : data[i]); + return r; + } + + // Select the closest point to the mouse in the current facet; for + // pointerX or pointerY, the orthogonal component of the distance is + // squashed, selecting primarily on the dominant dimension. Across facets, + // use unsquashed distance to determine the winner. + function pointermove(event) { + if (state.sticky || (event.pointerType === "mouse" && event.buttons === 1)) return; // dragging + let [xp, yp] = d3.pointer(event); + (xp -= tx), (yp -= ty); // correct for facets and band scales + const kpx = xp < dimensions.marginLeft || xp > dimensions.width - dimensions.marginRight ? 1 : kx; + const kpy = yp < dimensions.marginTop || yp > dimensions.height - dimensions.marginBottom ? 1 : ky; + let ii = null; + let ri = maxRadius * maxRadius; + for (const j of index) { + const dx = kpx * (px(j) - xp); + const dy = kpy * (py(j) - yp); + const rj = dx * dx + dy * dy; + if (rj <= ri) (ii = j), (ri = rj); + } + if (ii != null && (kx !== 1 || ky !== 1)) { + const dx = px(ii) - xp; + const dy = py(ii) - yp; + ri = dx * dx + dy * dy; + } + update(ii, ri); + } + + function pointerdown(event) { + if (event.pointerType !== "mouse") return; + if (i == null) return; // not pointing + if (state.sticky && state.roots.some((r) => r?.contains(event.target))) return; // stay sticky + if (state.sticky) (state.sticky = false), state.renders.forEach((r) => r(null)); // clear all pointers + else (state.sticky = true), render(i); + event.stopImmediatePropagation(); // suppress other pointers + } + + function pointerleave(event) { + if (event.pointerType !== "mouse") return; + if (!state.sticky) update(null); + } + + // We listen to the svg element; listening to the window instead would let + // us receive pointer events from farther away, but would also make it + // hard to know when to remove the listeners. (Using a mutation observer + // to watch the entire document is likely too expensive.) + svg.addEventListener("pointerenter", pointermove); + svg.addEventListener("pointermove", pointermove); + svg.addEventListener("pointerdown", pointerdown); + svg.addEventListener("pointerleave", pointerleave); + + return render(null); + }, render) + }; +} + +function pointer(options) { + return pointerK(1, 1, options); +} + +function pointerX(options) { + return pointerK(1, 0.01, options); +} + +function pointerY(options) { + return pointerK(0.01, 1, options); +} + +function anchorX$1({x1: X1, x2: X2, x: X = X1}, cx) { + return X1 && X2 ? (i) => (X1[i] + X2[i]) / 2 : X ? (i) => X[i] : () => cx; +} + +function anchorY$1({y1: Y1, y2: Y2, y: Y = Y1}, cy) { + return Y1 && Y2 ? (i) => (Y1[i] + Y2[i]) / 2 : Y ? (i) => Y[i] : () => cy; +} + +function inferFontVariant$2(scale) { + return isOrdinalScale(scale) && scale.interval === undefined ? undefined : "tabular-nums"; +} + +function legendRamp(color, options) { + let { + label = color.label, + tickSize = 6, + width = 240, + height = 44 + tickSize, + marginTop = 18, + marginRight = 0, + marginBottom = 16 + tickSize, + marginLeft = 0, + style, + ticks = (width - marginLeft - marginRight) / 64, + tickFormat, + fontVariant = inferFontVariant$2(color), + round = true, + opacity, + className + } = options; + const context = createContext(options); + className = maybeClassName(className); + opacity = maybeNumberChannel(opacity)[1]; + if (tickFormat === null) tickFormat = () => null; + + const svg = create("svg", context) + .attr("class", `${className}-ramp`) + .attr("font-family", "system-ui, sans-serif") + .attr("font-size", 10) + .attr("width", width) + .attr("height", height) + .attr("viewBox", `0 0 ${width} ${height}`) + .call((svg) => + // Warning: if you edit this, change defaultClassName. + svg.append("style").text( + `.${className}-ramp { + display: block; + height: auto; + height: intrinsic; + max-width: 100%; + overflow: visible; +} +.${className}-ramp text { + white-space: pre; +}` + ) + ) + .call(applyInlineStyles, style); + + let tickAdjust = (g) => g.selectAll(".tick line").attr("y1", marginTop + marginBottom - height); + + let x; + + // Some D3 scales use scale.interpolate, some scale.interpolator, and some + // scale.round; this normalizes the API so it works with all scale types. + const applyRange = round ? (x, range) => x.rangeRound(range) : (x, range) => x.range(range); + + const {type, domain, range, interpolate, scale, pivot} = color; + + // Continuous + if (interpolate) { + // Often interpolate is a “fixed” interpolator on the [0, 1] interval, as + // with a built-in color scheme, but sometimes it is a function that takes + // two arguments and is used in conjunction with the range. + const interpolator = + range === undefined + ? interpolate + : d3.piecewise(interpolate.length === 1 ? interpolatePiecewise(interpolate) : interpolate, range); + + // Construct a D3 scale of the same type, but with a range that evenly + // divides the horizontal extent of the legend. (In the common case, the + // domain.length is two, and so the range is simply the extent.) For a + // diverging scale, we need an extra point in the range for the pivot such + // that the pivot is always drawn in the middle. + x = applyRange( + scale.copy(), + d3.quantize( + d3.interpolateNumber(marginLeft, width - marginRight), + Math.min(domain.length + (pivot !== undefined), range === undefined ? Infinity : range.length) + ) + ); + + // Construct a 256×1 canvas, filling each pixel using the interpolator. + const n = 256; + const canvas = context.document.createElement("canvas"); + canvas.width = n; + canvas.height = 1; + const context2 = canvas.getContext("2d"); + for (let i = 0, j = n - 1; i < n; ++i) { + context2.fillStyle = interpolator(i / j); + context2.fillRect(i, 0, 1, 1); + } + + svg + .append("image") + .attr("opacity", opacity) + .attr("x", marginLeft) + .attr("y", marginTop) + .attr("width", width - marginLeft - marginRight) + .attr("height", height - marginTop - marginBottom) + .attr("preserveAspectRatio", "none") + .attr("xlink:href", canvas.toDataURL()); + } + + // Threshold + else if (type === "threshold") { + const thresholds = domain; + + const thresholdFormat = + tickFormat === undefined ? (d) => d : typeof tickFormat === "string" ? d3.format(tickFormat) : tickFormat; + + // Construct a linear scale with evenly-spaced ticks for each of the + // thresholds; the domain extends one beyond the threshold extent. + x = applyRange(d3.scaleLinear().domain([-1, range.length - 1]), [marginLeft, width - marginRight]); + + svg + .append("g") + .attr("fill-opacity", opacity) + .selectAll() + .data(range) + .enter() + .append("rect") + .attr("x", (d, i) => x(i - 1)) + .attr("y", marginTop) + .attr("width", (d, i) => x(i) - x(i - 1)) + .attr("height", height - marginTop - marginBottom) + .attr("fill", (d) => d); + + ticks = map$1(thresholds, (_, i) => i); + tickFormat = (i) => thresholdFormat(thresholds[i], i); + } + + // Ordinal (hopefully!) + else { + x = applyRange(d3.scaleBand().domain(domain), [marginLeft, width - marginRight]); + + svg + .append("g") + .attr("fill-opacity", opacity) + .selectAll() + .data(domain) + .enter() + .append("rect") + .attr("x", x) + .attr("y", marginTop) + .attr("width", Math.max(0, x.bandwidth() - 1)) + .attr("height", height - marginTop - marginBottom) + .attr("fill", scale); + + tickAdjust = () => {}; + } + + svg + .append("g") + .attr("transform", `translate(0,${height - marginBottom})`) + .call( + d3.axisBottom(x) + .ticks(Array.isArray(ticks) ? null : ticks, typeof tickFormat === "string" ? tickFormat : undefined) + .tickFormat(typeof tickFormat === "function" ? tickFormat : undefined) + .tickSize(tickSize) + .tickValues(Array.isArray(ticks) ? ticks : null) + ) + .attr("font-size", null) + .attr("font-family", null) + .attr("font-variant", impliedString(fontVariant, "normal")) + .call(tickAdjust) + .call((g) => g.select(".domain").remove()); + + if (label !== undefined) { + svg + .append("text") + .attr("x", marginLeft) + .attr("y", marginTop - 6) + .attr("fill", "currentColor") // TODO move to stylesheet? + .attr("font-weight", "bold") + .text(label); + } + + return svg.node(); +} + +const radians = Math.PI / 180; + +function markers(mark, {marker, markerStart = marker, markerMid = marker, markerEnd = marker} = {}) { + mark.markerStart = maybeMarker(markerStart); + mark.markerMid = maybeMarker(markerMid); + mark.markerEnd = maybeMarker(markerEnd); +} + +function maybeMarker(marker) { + if (marker == null || marker === false) return null; + if (marker === true) return markerCircleFill; + if (typeof marker === "function") return marker; + switch (`${marker}`.toLowerCase()) { + case "none": + return null; + case "arrow": + return markerArrow("auto"); + case "arrow-reverse": + return markerArrow("auto-start-reverse"); + case "dot": + return markerDot; + case "circle": + case "circle-fill": + return markerCircleFill; + case "circle-stroke": + return markerCircleStroke; + case "tick": + return markerTick("auto"); + case "tick-x": + return markerTick(90); + case "tick-y": + return markerTick(0); + } + throw new Error(`invalid marker: ${marker}`); +} + +function markerArrow(orient) { + return (color, context) => + create("svg:marker", context) + .attr("viewBox", "-5 -5 10 10") + .attr("markerWidth", 6.67) + .attr("markerHeight", 6.67) + .attr("orient", orient) + .attr("fill", "none") + .attr("stroke", color) + .attr("stroke-width", 1.5) + .attr("stroke-linecap", "round") + .attr("stroke-linejoin", "round") + .call((marker) => marker.append("path").attr("d", "M-1.5,-3l3,3l-3,3")) + .node(); +} + +function markerDot(color, context) { + return create("svg:marker", context) + .attr("viewBox", "-5 -5 10 10") + .attr("markerWidth", 6.67) + .attr("markerHeight", 6.67) + .attr("fill", color) + .attr("stroke", "none") + .call((marker) => marker.append("circle").attr("r", 2.5)) + .node(); +} + +function markerCircleFill(color, context) { + return create("svg:marker", context) + .attr("viewBox", "-5 -5 10 10") + .attr("markerWidth", 6.67) + .attr("markerHeight", 6.67) + .attr("fill", color) + .attr("stroke", "var(--plot-background)") + .attr("stroke-width", 1.5) + .call((marker) => marker.append("circle").attr("r", 3)) + .node(); +} + +function markerCircleStroke(color, context) { + return create("svg:marker", context) + .attr("viewBox", "-5 -5 10 10") + .attr("markerWidth", 6.67) + .attr("markerHeight", 6.67) + .attr("fill", "var(--plot-background)") + .attr("stroke", color) + .attr("stroke-width", 1.5) + .call((marker) => marker.append("circle").attr("r", 3)) + .node(); +} + +function markerTick(orient) { + return (color, context) => + create("svg:marker", context) + .attr("viewBox", "-3 -3 6 6") + .attr("markerWidth", 6) + .attr("markerHeight", 6) + .attr("orient", orient) + .attr("stroke", color) + .call((marker) => marker.append("path").attr("d", "M0,-3v6")) + .node(); +} + +let nextMarkerId = 0; + +function applyMarkers(path, mark, {stroke: S}, context) { + return applyMarkersColor(path, mark, S && ((i) => S[i]), context); +} + +function applyGroupedMarkers(path, mark, {stroke: S}, context) { + return applyMarkersColor(path, mark, S && (([i]) => S[i]), context); +} + +function applyMarkersColor(path, {markerStart, markerMid, markerEnd, stroke}, strokeof = () => stroke, context) { + const iriByMarkerColor = new Map(); + + function applyMarker(marker) { + return function (i) { + const color = strokeof(i); + let iriByColor = iriByMarkerColor.get(marker); + if (!iriByColor) iriByMarkerColor.set(marker, (iriByColor = new Map())); + let iri = iriByColor.get(color); + if (!iri) { + const node = this.parentNode.insertBefore(marker(color, context), this); + const id = `plot-marker-${++nextMarkerId}`; + node.setAttribute("id", id); + iriByColor.set(color, (iri = `url(#${id})`)); + } + return iri; + }; + } + + if (markerStart) path.attr("marker-start", applyMarker(markerStart)); + if (markerMid) path.attr("marker-mid", applyMarker(markerMid)); + if (markerEnd) path.attr("marker-end", applyMarker(markerEnd)); +} + +function maybeInsetX({inset, insetLeft, insetRight, ...options} = {}) { + [insetLeft, insetRight] = maybeInset(inset, insetLeft, insetRight); + return {inset, insetLeft, insetRight, ...options}; +} + +function maybeInsetY({inset, insetTop, insetBottom, ...options} = {}) { + [insetTop, insetBottom] = maybeInset(inset, insetTop, insetBottom); + return {inset, insetTop, insetBottom, ...options}; +} + +function maybeInset(inset, inset1, inset2) { + return inset === undefined && inset1 === undefined && inset2 === undefined + ? offset + ? [1, 0] + : [0.5, 0.5] + : [inset1, inset2]; +} + +// The interval may be specified either as x: {value, interval} or as {x, +// interval}. The former can be used to specify separate intervals for x and y, +// for example with Plot.rect. +function maybeIntervalValue(value, {interval}) { + value = {...maybeValue(value)}; + value.interval = maybeInterval(value.interval === undefined ? interval : value.interval); + return value; +} + +function maybeIntervalK(k, maybeInsetK, options, trivial) { + const {[k]: v, [`${k}1`]: v1, [`${k}2`]: v2} = options; + const {value, interval} = maybeIntervalValue(v, options); + if (value == null || (interval == null && !trivial)) return options; + const label = labelof(v); + if (interval == null) { + let V; + const kv = {transform: (data) => V || (V = valueof(data, value)), label}; + return { + ...options, + [k]: undefined, + [`${k}1`]: v1 === undefined ? kv : v1, + [`${k}2`]: v2 === undefined && !(v1 === v2 && trivial) ? kv : v2 + }; + } + let D1, V1; + function transform(data) { + if (V1 !== undefined && data === D1) return V1; // memoize + return (V1 = map$1(valueof((D1 = data), value), (v) => interval.floor(v))); + } + return maybeInsetK({ + ...options, + [k]: undefined, + [`${k}1`]: v1 === undefined ? {transform, label} : v1, + [`${k}2`]: v2 === undefined ? {transform: (data) => transform(data).map((v) => interval.offset(v)), label} : v2 + }); +} + +function maybeIntervalMidK(k, maybeInsetK, options) { + const {[k]: v} = options; + const {value, interval} = maybeIntervalValue(v, options); + if (value == null || interval == null) return options; + return maybeInsetK({ + ...options, + [k]: { + label: labelof(v), + transform: (data) => { + const V1 = map$1(valueof(data, value), (v) => interval.floor(v)); + const V2 = V1.map((v) => interval.offset(v)); + return V1.map( + isTemporal(V1) + ? (v1, v2) => + v1 == null || isNaN((v1 = +v1)) || ((v2 = V2[v2]), v2 == null) || isNaN((v2 = +v2)) + ? undefined + : new Date((v1 + v2) / 2) + : (v1, v2) => (v1 == null || ((v2 = V2[v2]), v2 == null) ? NaN : (+v1 + +v2) / 2) + ); + } + } + }); +} + +function maybeTrivialIntervalX(options = {}) { + return maybeIntervalK("x", maybeInsetX, options, true); +} + +function maybeTrivialIntervalY(options = {}) { + return maybeIntervalK("y", maybeInsetY, options, true); +} + +function maybeIntervalX(options = {}) { + return maybeIntervalK("x", maybeInsetX, options); +} + +function maybeIntervalY(options = {}) { + return maybeIntervalK("y", maybeInsetY, options); +} + +function maybeIntervalMidX(options = {}) { + return maybeIntervalMidK("x", maybeInsetX, options); +} + +function maybeIntervalMidY(options = {}) { + return maybeIntervalMidK("y", maybeInsetY, options); +} + +const defaults$l = { + ariaLabel: "rule", + fill: null, + stroke: "currentColor" +}; + +class RuleX extends Mark { + constructor(data, options = {}) { + const {x, y1, y2, inset = 0, insetTop = inset, insetBottom = inset} = options; + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y1: {value: y1, scale: "y", optional: true}, + y2: {value: y2, scale: "y", optional: true} + }, + withTip(options, "x"), + defaults$l + ); + this.insetTop = number$1(insetTop); + this.insetBottom = number$1(insetBottom); + markers(this, options); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x: X, y1: Y1, y2: Y2} = channels; + const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions; + const {insetTop, insetBottom} = this; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {x: X && x}, offset, 0) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("line") + .call(applyDirectStyles, this) + .attr("x1", X ? (i) => X[i] : (marginLeft + width - marginRight) / 2) + .attr("x2", X ? (i) => X[i] : (marginLeft + width - marginRight) / 2) + .attr("y1", Y1 && !isCollapsed(y) ? (i) => Y1[i] + insetTop : marginTop + insetTop) + .attr( + "y2", + Y2 && !isCollapsed(y) + ? y.bandwidth + ? (i) => Y2[i] + y.bandwidth() - insetBottom + : (i) => Y2[i] - insetBottom + : height - marginBottom - insetBottom + ) + .call(applyChannelStyles, this, channels) + .call(applyMarkers, this, channels, context) + ) + .node(); + } +} + +class RuleY extends Mark { + constructor(data, options = {}) { + const {x1, x2, y, inset = 0, insetRight = inset, insetLeft = inset} = options; + super( + data, + { + y: {value: y, scale: "y", optional: true}, + x1: {value: x1, scale: "x", optional: true}, + x2: {value: x2, scale: "x", optional: true} + }, + withTip(options, "y"), + defaults$l + ); + this.insetRight = number$1(insetRight); + this.insetLeft = number$1(insetLeft); + markers(this, options); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {y: Y, x1: X1, x2: X2} = channels; + const {width, height, marginTop, marginRight, marginLeft, marginBottom} = dimensions; + const {insetLeft, insetRight} = this; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {y: Y && y}, 0, offset) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("line") + .call(applyDirectStyles, this) + .attr("x1", X1 && !isCollapsed(x) ? (i) => X1[i] + insetLeft : marginLeft + insetLeft) + .attr( + "x2", + X2 && !isCollapsed(x) + ? x.bandwidth + ? (i) => X2[i] + x.bandwidth() - insetRight + : (i) => X2[i] - insetRight + : width - marginRight - insetRight + ) + .attr("y1", Y ? (i) => Y[i] : (marginTop + height - marginBottom) / 2) + .attr("y2", Y ? (i) => Y[i] : (marginTop + height - marginBottom) / 2) + .call(applyChannelStyles, this, channels) + .call(applyMarkers, this, channels, context) + ) + .node(); + } +} + +function ruleX(data, options) { + let {x = identity$1, y, y1, y2, ...rest} = maybeIntervalY(options); + [y1, y2] = maybeOptionalZero(y, y1, y2); + return new RuleX(data, {...rest, x, y1, y2}); +} + +function ruleY(data, options) { + let {y = identity$1, x, x1, x2, ...rest} = maybeIntervalX(options); + [x1, x2] = maybeOptionalZero(x, x1, x2); + return new RuleY(data, {...rest, y, x1, x2}); +} + +// For marks specified either as [0, x] or [x1, x2], or nothing. +function maybeOptionalZero(x, x1, x2) { + if (x == null) { + if (x1 === undefined) { + if (x2 !== undefined) return [0, x2]; + } else { + if (x2 === undefined) return [0, x1]; + } + } else if (x1 === undefined) { + return x2 === undefined ? [0, x] : [x, x2]; + } else if (x2 === undefined) { + return [x, x1]; + } + return [x1, x2]; +} + +function template(strings, ...parts) { + let n = parts.length; + + // If any of the interpolated parameters are strings rather than functions, + // bake them into the template to optimize performance during render. + for (let j = 0, copy = true; j < n; ++j) { + if (typeof parts[j] !== "function") { + if (copy) { + strings = strings.slice(); // copy before mutate + copy = false; + } + strings.splice(j, 2, strings[j] + parts[j] + strings[j + 1]); + parts.splice(j, 1); + --j, --n; + } + } + + return (i) => { + let s = strings[0]; + for (let j = 0; j < n; ++j) { + s += parts[j](i) + strings[j + 1]; + } + return s; + }; +} + +const defaults$k = { + ariaLabel: "text", + strokeLinejoin: "round", + strokeWidth: 3, + paintOrder: "stroke" +}; + +const softHyphen = "\u00ad"; + +class Text extends Mark { + constructor(data, options = {}) { + const { + x, + y, + text = isIterable(data) && isTextual(data) ? identity$1 : indexOf, + frameAnchor, + textAnchor = /right$/i.test(frameAnchor) ? "end" : /left$/i.test(frameAnchor) ? "start" : "middle", + lineAnchor = /^top/i.test(frameAnchor) ? "top" : /^bottom/i.test(frameAnchor) ? "bottom" : "middle", + lineHeight = 1, + lineWidth = Infinity, + textOverflow, + monospace, + fontFamily = monospace ? "ui-monospace, monospace" : undefined, + fontSize, + fontStyle, + fontVariant, + fontWeight, + rotate + } = options; + const [vrotate, crotate] = maybeNumberChannel(rotate, 0); + const [vfontSize, cfontSize] = maybeFontSizeChannel(fontSize); + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + fontSize: {value: vfontSize, optional: true}, + rotate: {value: numberChannel(vrotate), optional: true}, + text: {value: text, filter: nonempty, optional: true} + }, + options, + defaults$k + ); + this.rotate = crotate; + this.textAnchor = impliedString(textAnchor, "middle"); + this.lineAnchor = keyword(lineAnchor, "lineAnchor", ["top", "middle", "bottom"]); + this.lineHeight = +lineHeight; + this.lineWidth = +lineWidth; + this.textOverflow = maybeTextOverflow(textOverflow); + this.monospace = !!monospace; + this.fontFamily = string(fontFamily); + this.fontSize = cfontSize; + this.fontStyle = string(fontStyle); + this.fontVariant = string(fontVariant); + this.fontWeight = string(fontWeight); + this.frameAnchor = maybeFrameAnchor(frameAnchor); + if (!(this.lineWidth >= 0)) throw new Error(`invalid lineWidth: ${lineWidth}`); + this.splitLines = splitter(this); + this.clipLine = clipper(this); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x: X, y: Y, rotate: R, text: T, title: TL, fontSize: FS} = channels; + const {rotate} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyIndirectTextStyles, this, T, dimensions) + .call(applyTransform, this, {x: X && x, y: Y && y}) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("text") + .call(applyDirectStyles, this) + .call(applyMultilineText, this, T, TL) + .attr( + "transform", + template`translate(${X ? (i) => X[i] : cx},${Y ? (i) => Y[i] : cy})${ + R ? (i) => ` rotate(${R[i]})` : rotate ? ` rotate(${rotate})` : `` + }` + ) + .call(applyAttr, "font-size", FS && ((i) => FS[i])) + .call(applyChannelStyles, this, channels) + ) + .node(); + } +} + +function maybeTextOverflow(textOverflow) { + return textOverflow == null + ? null + : keyword(textOverflow, "textOverflow", [ + "clip", // shorthand for clip-end + "ellipsis", // … ellipsis-end + "clip-start", + "clip-end", + "ellipsis-start", + "ellipsis-middle", + "ellipsis-end" + ]).replace(/^(clip|ellipsis)$/, "$1-end"); +} + +function applyMultilineText(selection, mark, T, TL) { + if (!T) return; + const {lineAnchor, lineHeight, textOverflow, splitLines, clipLine} = mark; + selection.each(function (i) { + const lines = splitLines(formatDefault(T[i]) ?? "").map(clipLine); + const n = lines.length; + const y = lineAnchor === "top" ? 0.71 : lineAnchor === "bottom" ? 1 - n : (164 - n * 100) / 200; + if (n > 1) { + let m = 0; + for (let i = 0; i < n; ++i) { + ++m; + if (!lines[i]) continue; + const tspan = this.ownerDocument.createElementNS(d3.namespaces.svg, "tspan"); + tspan.setAttribute("x", 0); + if (i === m - 1) tspan.setAttribute("y", `${(y + i) * lineHeight}em`); + else tspan.setAttribute("dy", `${m * lineHeight}em`); + tspan.textContent = lines[i]; + this.appendChild(tspan); + m = 0; + } + } else { + if (y) this.setAttribute("y", `${y * lineHeight}em`); + this.textContent = lines[0]; + } + if (textOverflow && !TL && lines[0] !== T[i]) { + const title = this.ownerDocument.createElementNS(d3.namespaces.svg, "title"); + title.textContent = T[i]; + this.appendChild(title); + } + }); +} + +function text(data, {x, y, ...options} = {}) { + if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); + return new Text(data, {...options, x, y}); +} + +function textX(data, {x = identity$1, ...options} = {}) { + return new Text(data, maybeIntervalMidY({...options, x})); +} + +function textY(data, {y = identity$1, ...options} = {}) { + return new Text(data, maybeIntervalMidX({...options, y})); +} + +function applyIndirectTextStyles(selection, mark, T) { + applyAttr(selection, "text-anchor", mark.textAnchor); + applyAttr(selection, "font-family", mark.fontFamily); + applyAttr(selection, "font-size", mark.fontSize); + applyAttr(selection, "font-style", mark.fontStyle); + applyAttr(selection, "font-variant", mark.fontVariant === undefined ? inferFontVariant$1(T) : mark.fontVariant); + applyAttr(selection, "font-weight", mark.fontWeight); +} + +function inferFontVariant$1(T) { + return T && (isNumeric(T) || isTemporal(T)) ? "tabular-nums" : undefined; +} + +// https://developer.mozilla.org/en-US/docs/Web/CSS/font-size +const fontSizes = new Set([ + // global keywords + "inherit", + "initial", + "revert", + "unset", + // absolute keywords + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "xxx-large", + // relative keywords + "larger", + "smaller" +]); + +// The font size may be expressed as a constant in the following forms: +// - number in pixels +// - string keyword: see above +// - string <length>: e.g., "12px" +// - string <percentage>: e.g., "80%" +// Anything else is assumed to be a channel definition. +function maybeFontSizeChannel(fontSize) { + if (fontSize == null || typeof fontSize === "number") return [undefined, fontSize]; + if (typeof fontSize !== "string") return [fontSize, undefined]; + fontSize = fontSize.trim().toLowerCase(); + return fontSizes.has(fontSize) || /^[+-]?\d*\.?\d+(e[+-]?\d+)?(\w*|%)$/.test(fontSize) + ? [undefined, fontSize] + : [fontSize, undefined]; +} + +// This is a greedy algorithm for line wrapping. It would be better to use the +// Knuth–Plass line breaking algorithm (but that would be much more complex). +// https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap +function lineWrap(input, maxWidth, widthof) { + const lines = []; + let lineStart, + lineEnd = 0; + for (const [wordStart, wordEnd, required] of lineBreaks(input)) { + // Record the start of a line. This isn’t the same as the previous line’s + // end because we often skip spaces between lines. + if (lineStart === undefined) lineStart = wordStart; + + // If the current line is not empty, and if adding the current word would + // make the line longer than the allowed width, then break the line at the + // previous word end. + if (lineEnd > lineStart && widthof(input, lineStart, wordEnd) > maxWidth) { + lines.push(input.slice(lineStart, lineEnd) + (input[lineEnd - 1] === softHyphen ? "-" : "")); + lineStart = wordStart; + } + + // If this is a required break (a newline), emit the line and reset. + if (required) { + lines.push(input.slice(lineStart, wordEnd)); + lineStart = undefined; + continue; + } + + // Extend the current line to include the new word. + lineEnd = wordEnd; + } + return lines; +} + +// This is a rudimentary (and U.S.-centric) algorithm for finding opportunities +// to break lines between words. A better and far more comprehensive approach +// would be to use the official Unicode Line Breaking Algorithm. +// https://unicode.org/reports/tr14/ +function* lineBreaks(input) { + let i = 0, + j = 0; + const n = input.length; + while (j < n) { + let k = 1; + switch (input[j]) { + case softHyphen: + case "-": // hyphen + ++j; + yield [i, j, false]; + i = j; + break; + case " ": + yield [i, j, false]; + while (input[++j] === " "); // skip multiple spaces + i = j; + break; + case "\r": + if (input[j + 1] === "\n") ++k; // falls through + case "\n": + yield [i, j, true]; + j += k; + i = j; + break; + default: + ++j; + break; + } + } + yield [i, j, true]; +} + +// Computed as round(measureText(text).width * 10) at 10px system-ui. For +// characters that are not represented in this map, we’d ideally want to use a +// weighted average of what we expect to see. But since we don’t really know +// what that is, using “e” seems reasonable. +const defaultWidthMap = { + a: 56, + b: 63, + c: 57, + d: 63, + e: 58, + f: 37, + g: 62, + h: 60, + i: 26, + j: 26, + k: 55, + l: 26, + m: 88, + n: 60, + o: 60, + p: 62, + q: 62, + r: 39, + s: 54, + t: 38, + u: 60, + v: 55, + w: 79, + x: 54, + y: 55, + z: 55, + A: 69, + B: 67, + C: 73, + D: 74, + E: 61, + F: 58, + G: 76, + H: 75, + I: 28, + J: 55, + K: 67, + L: 58, + M: 89, + N: 75, + O: 78, + P: 65, + Q: 78, + R: 67, + S: 65, + T: 65, + U: 75, + V: 69, + W: 98, + X: 69, + Y: 67, + Z: 67, + 0: 64, + 1: 48, + 2: 62, + 3: 64, + 4: 66, + 5: 63, + 6: 65, + 7: 58, + 8: 65, + 9: 65, + " ": 29, + "!": 32, + '"': 49, + "'": 31, + "(": 39, + ")": 39, + ",": 31, + "-": 48, + ".": 31, + "/": 32, + ":": 31, + ";": 31, + "?": 52, + "‘": 31, + "’": 31, + "“": 47, + "”": 47, + "…": 82 +}; + +// This is a rudimentary (and U.S.-centric) algorithm for measuring the width of +// a string based on a technique of Gregor Aisch; it assumes that individual +// characters are laid out independently and does not implement the Unicode +// grapheme cluster breaking algorithm. It does understand code points, though, +// and so treats things like emoji as having the width of a lowercase e (and +// should be equivalent to using for-of to iterate over code points, while also +// being fast). TODO Optimize this by noting that we often re-measure characters +// that were previously measured? +// http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries +// https://exploringjs.com/impatient-js/ch_strings.html#atoms-of-text +function defaultWidth(text, start = 0, end = text.length) { + let sum = 0; + for (let i = start; i < end; i = readCharacter(text, i)) { + sum += defaultWidthMap[text[i]] ?? (isPictographic(text, i) ? 120 : defaultWidthMap.e); + } + return sum; +} + +// Even for monospaced text, we can’t assume that the number of UTF-16 code +// points (i.e., the length of a string) corresponds to the number of visible +// characters; we still have to count graphemes. And note that pictographic +// characters such as emojis are typically not monospaced! +function monospaceWidth(text, start = 0, end = text.length) { + let sum = 0; + for (let i = start; i < end; i = readCharacter(text, i)) { + sum += isPictographic(text, i) ? 126 : 63; + } + return sum; +} + +function splitter({monospace, lineWidth, textOverflow}) { + if (textOverflow != null || lineWidth == Infinity) return (text) => text.split(/\r\n?|\n/g); + const widthof = monospace ? monospaceWidth : defaultWidth; + const maxWidth = lineWidth * 100; + return (text) => lineWrap(text, maxWidth, widthof); +} + +function clipper({monospace, lineWidth, textOverflow}) { + if (textOverflow == null || lineWidth == Infinity) return (text) => text; + const widthof = monospace ? monospaceWidth : defaultWidth; + const maxWidth = lineWidth * 100; + switch (textOverflow) { + case "clip-start": + return (text) => clipStart(text, maxWidth, widthof, ""); + case "clip-end": + return (text) => clipEnd(text, maxWidth, widthof, ""); + case "ellipsis-start": + return (text) => clipStart(text, maxWidth, widthof, ellipsis); + case "ellipsis-middle": + return (text) => clipMiddle(text, maxWidth, widthof, ellipsis); + case "ellipsis-end": + return (text) => clipEnd(text, maxWidth, widthof, ellipsis); + } +} + +const ellipsis = "…"; + +// Cuts the given text to the given width, using the specified widthof function; +// the returned [index, error] guarantees text.slice(0, index) fits within the +// specified width with the given error. If the text fits naturally within the +// given width, returns [-1, 0]. If the text needs cutting, the given inset +// specifies how much space (in the same units as width and widthof) to reserve +// for a possible ellipsis character. +function cut(text, width, widthof, inset) { + const I = []; // indexes of read character boundaries + let w = 0; // current line width + for (let i = 0, j = 0, n = text.length; i < n; i = j) { + j = readCharacter(text, i); // read the next character + const l = widthof(text, i, j); // current character width + if (w + l > width) { + w += inset; + while (w > width && i > 0) (j = i), (i = I.pop()), (w -= widthof(text, i, j)); // remove excess + return [i, width - w]; + } + w += l; + I.push(i); + } + return [-1, 0]; +} + +function clipEnd(text, width, widthof, ellipsis) { + text = text.trim(); // ignore leading and trailing whitespace + const e = widthof(ellipsis); + const [i] = cut(text, width, widthof, e); + return i < 0 ? text : text.slice(0, i).trimEnd() + ellipsis; +} + +function clipMiddle(text, width, widthof, ellipsis) { + text = text.trim(); // ignore leading and trailing whitespace + const w = widthof(text); + if (w <= width) return text; + const e = widthof(ellipsis) / 2; + const [i, ei] = cut(text, width / 2, widthof, e); + const [j] = cut(text, w - width / 2 - ei + e, widthof, -e); // TODO read spaces? + return j < 0 ? ellipsis : text.slice(0, i).trimEnd() + ellipsis + text.slice(readCharacter(text, j)).trimStart(); +} + +function clipStart(text, width, widthof, ellipsis) { + text = text.trim(); // ignore leading and trailing whitespace + const w = widthof(text); + if (w <= width) return text; + const e = widthof(ellipsis); + const [j] = cut(text, w - width + e, widthof, -e); // TODO read spaces? + return j < 0 ? ellipsis : ellipsis + text.slice(readCharacter(text, j)).trimStart(); +} + +const reCombiner = /[\p{Combining_Mark}\p{Emoji_Modifier}]+/uy; +const rePictographic = /\p{Extended_Pictographic}/uy; + +// Reads a single “character” element from the given text starting at the given +// index, returning the index after the read character. Ideally, this implements +// the Unicode text segmentation algorithm and understands grapheme cluster +// boundaries, etc., but in practice this is only smart enough to detect UTF-16 +// surrogate pairs, combining marks, and zero-width joiner (zwj) sequences such +// as emoji skin color modifiers. https://unicode.org/reports/tr29/ +function readCharacter(text, i) { + i += isSurrogatePair(text, i) ? 2 : 1; + if (isCombiner(text, i)) i = reCombiner.lastIndex; + if (isZeroWidthJoiner(text, i)) return readCharacter(text, i + 1); + return i; +} + +// We avoid more expensive regex tests involving Unicode property classes by +// first checking for the common case of 7-bit ASCII characters. +function isAscii(text, i) { + return text.charCodeAt(i) < 0x80; +} + +function isSurrogatePair(text, i) { + const hi = text.charCodeAt(i); + if (hi >= 0xd800 && hi < 0xdc00) { + const lo = text.charCodeAt(i + 1); + return lo >= 0xdc00 && lo < 0xe000; + } + return false; +} + +function isZeroWidthJoiner(text, i) { + return text.charCodeAt(i) === 0x200d; +} + +function isCombiner(text, i) { + return isAscii(text, i) ? false : ((reCombiner.lastIndex = i), reCombiner.test(text)); +} + +function isPictographic(text, i) { + return isAscii(text, i) ? false : ((rePictographic.lastIndex = i), rePictographic.test(text)); +} + +const defaults$j = { + ariaLabel: "vector", + fill: "none", + stroke: "currentColor", + strokeWidth: 1.5, + strokeLinejoin: "round", + strokeLinecap: "round" +}; + +const defaultRadius = 3.5; + +// The size of the arrowhead is proportional to its length, but we still allow +// the relative size of the head to be controlled via the mark’s width option; +// doubling the default radius will produce an arrowhead that is twice as big. +// That said, we’ll probably want a arrow with a fixed head size, too. +const wingRatio = defaultRadius * 5; + +const shapeArrow = { + draw(context, l, r) { + const wing = (l * r) / wingRatio; + context.moveTo(0, 0); + context.lineTo(0, -l); + context.moveTo(-wing, wing - l); + context.lineTo(0, -l); + context.lineTo(wing, wing - l); + } +}; + +const shapeSpike = { + draw(context, l, r) { + context.moveTo(-r, 0); + context.lineTo(0, -l); + context.lineTo(r, 0); + } +}; + +const shapes = new Map([ + ["arrow", shapeArrow], + ["spike", shapeSpike] +]); + +function isShapeObject(value) { + return value && typeof value.draw === "function"; +} + +function maybeShape(shape) { + if (isShapeObject(shape)) return shape; + const value = shapes.get(`${shape}`.toLowerCase()); + if (value) return value; + throw new Error(`invalid shape: ${shape}`); +} + +class Vector extends Mark { + constructor(data, options = {}) { + const {x, y, r = defaultRadius, length, rotate, shape = shapeArrow, anchor = "middle", frameAnchor} = options; + const [vl, cl] = maybeNumberChannel(length, 12); + const [vr, cr] = maybeNumberChannel(rotate, 0); + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + length: {value: vl, scale: "length", optional: true}, + rotate: {value: vr, optional: true} + }, + options, + defaults$j + ); + this.r = +r; + this.length = cl; + this.rotate = cr; + this.shape = maybeShape(shape); + this.anchor = keyword(anchor, "anchor", ["start", "middle", "end"]); + this.frameAnchor = maybeFrameAnchor(frameAnchor); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x: X, y: Y, length: L, rotate: A} = channels; + const {length, rotate, anchor, shape, r} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {x: X && x, y: Y && y}) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("path") + .call(applyDirectStyles, this) + .attr( + "transform", + template`translate(${X ? (i) => X[i] : cx},${Y ? (i) => Y[i] : cy})${ + A ? (i) => ` rotate(${A[i]})` : rotate ? ` rotate(${rotate})` : `` + }${ + anchor === "start" + ? `` + : anchor === "end" + ? L + ? (i) => ` translate(0,${L[i]})` + : ` translate(0,${length})` + : L + ? (i) => ` translate(0,${L[i] / 2})` + : ` translate(0,${length / 2})` + }` + ) + .attr( + "d", + L + ? (i) => { + const p = d3.pathRound(); + shape.draw(p, L[i], r); + return p; + } + : (() => { + const p = d3.pathRound(); + shape.draw(p, length, r); + return p; + })() + ) + .call(applyChannelStyles, this, channels) + ) + .node(); + } +} + +function vector(data, options = {}) { + let {x, y, ...rest} = options; + if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); + return new Vector(data, {...rest, x, y}); +} + +function vectorX(data, options = {}) { + const {x = identity$1, ...rest} = options; + return new Vector(data, {...rest, x}); +} + +function vectorY(data, options = {}) { + const {y = identity$1, ...rest} = options; + return new Vector(data, {...rest, y}); +} + +function spike(data, options = {}) { + const { + shape = shapeSpike, + stroke = defaults$j.stroke, + strokeWidth = 1, + fill = stroke, + fillOpacity = 0.3, + anchor = "start", + ...rest + } = options; + return vector(data, {...rest, shape, stroke, strokeWidth, fill, fillOpacity, anchor}); +} + +function maybeData(data, options) { + if (arguments.length < 2 && !isIterable(data)) (options = data), (data = null); + if (options === undefined) options = {}; + return [data, options]; +} + +function maybeAnchor$2({anchor} = {}, anchors) { + return anchor === undefined ? anchors[0] : keyword(anchor, "anchor", anchors); +} + +function anchorY(options) { + return maybeAnchor$2(options, ["left", "right"]); +} + +function anchorFy(options) { + return maybeAnchor$2(options, ["right", "left"]); +} + +function anchorX(options) { + return maybeAnchor$2(options, ["bottom", "top"]); +} + +function anchorFx(options) { + return maybeAnchor$2(options, ["top", "bottom"]); +} + +function axisY() { + const [data, options] = maybeData(...arguments); + return axisKy("y", anchorY(options), data, options); +} + +function axisFy() { + const [data, options] = maybeData(...arguments); + return axisKy("fy", anchorFy(options), data, options); +} + +function axisX() { + const [data, options] = maybeData(...arguments); + return axisKx("x", anchorX(options), data, options); +} + +function axisFx() { + const [data, options] = maybeData(...arguments); + return axisKx("fx", anchorFx(options), data, options); +} + +function axisKy( + k, + anchor, + data, + { + color = "currentColor", + opacity = 1, + stroke = color, + strokeOpacity = opacity, + strokeWidth = 1, + fill = color, + fillOpacity = opacity, + textAnchor, + textStroke, + textStrokeOpacity, + textStrokeWidth, + tickSize = k === "y" ? 6 : 0, + tickPadding, + tickRotate, + x, + margin, + marginTop = margin === undefined ? 20 : margin, + marginRight = margin === undefined ? (anchor === "right" ? 40 : 0) : margin, + marginBottom = margin === undefined ? 20 : margin, + marginLeft = margin === undefined ? (anchor === "left" ? 40 : 0) : margin, + label, + labelAnchor, + labelArrow, + labelOffset, + ...options + } +) { + tickSize = number$1(tickSize); + tickPadding = number$1(tickPadding); + tickRotate = number$1(tickRotate); + if (labelAnchor !== undefined) labelAnchor = keyword(labelAnchor, "labelAnchor", ["center", "top", "bottom"]); + labelArrow = maybeLabelArrow(labelArrow); + return marks( + tickSize && !isNoneish(stroke) + ? axisTickKy(k, anchor, data, { + stroke, + strokeOpacity, + strokeWidth, + tickSize, + tickPadding, + tickRotate, + x, + ...options + }) + : null, + !isNoneish(fill) + ? axisTextKy(k, anchor, data, { + fill, + fillOpacity, + stroke: textStroke, + strokeOpacity: textStrokeOpacity, + strokeWidth: textStrokeWidth, + textAnchor, + tickSize, + tickPadding, + tickRotate, + x, + marginTop, + marginRight, + marginBottom, + marginLeft, + ...options + }) + : null, + !isNoneish(fill) && label !== null + ? text( + [], + labelOptions({fill, fillOpacity, ...options}, function (data, facets, channels, scales, dimensions) { + const scale = scales[k]; + const {marginTop, marginRight, marginBottom, marginLeft} = (k === "y" && dimensions.inset) || dimensions; + const cla = labelAnchor ?? (scale.bandwidth ? "center" : "top"); + const clo = labelOffset ?? (anchor === "right" ? marginRight : marginLeft) - 3; + if (cla === "center") { + this.textAnchor = undefined; // middle + this.lineAnchor = anchor === "right" ? "bottom" : "top"; + this.frameAnchor = anchor; + this.rotate = -90; + } else { + this.textAnchor = anchor === "right" ? "end" : "start"; + this.lineAnchor = cla; + this.frameAnchor = `${cla}-${anchor}`; + this.rotate = 0; + } + this.dy = cla === "top" ? 3 - marginTop : cla === "bottom" ? marginBottom - 3 : 0; + this.dx = anchor === "right" ? clo : -clo; + this.ariaLabel = `${k}-axis label`; + return { + facets: [[0]], + channels: {text: {value: [formatAxisLabel(k, scale, {anchor, label, labelAnchor: cla, labelArrow})]}} + }; + }) + ) + : null + ); +} + +function axisKx( + k, + anchor, + data, + { + color = "currentColor", + opacity = 1, + stroke = color, + strokeOpacity = opacity, + strokeWidth = 1, + fill = color, + fillOpacity = opacity, + textAnchor, + textStroke, + textStrokeOpacity, + textStrokeWidth, + tickSize = k === "x" ? 6 : 0, + tickPadding, + tickRotate, + y, + margin, + marginTop = margin === undefined ? (anchor === "top" ? 30 : 0) : margin, + marginRight = margin === undefined ? 20 : margin, + marginBottom = margin === undefined ? (anchor === "bottom" ? 30 : 0) : margin, + marginLeft = margin === undefined ? 20 : margin, + label, + labelAnchor, + labelArrow, + labelOffset, + ...options + } +) { + tickSize = number$1(tickSize); + tickPadding = number$1(tickPadding); + tickRotate = number$1(tickRotate); + if (labelAnchor !== undefined) labelAnchor = keyword(labelAnchor, "labelAnchor", ["center", "left", "right"]); + labelArrow = maybeLabelArrow(labelArrow); + return marks( + tickSize && !isNoneish(stroke) + ? axisTickKx(k, anchor, data, { + stroke, + strokeOpacity, + strokeWidth, + tickSize, + tickPadding, + tickRotate, + y, + ...options + }) + : null, + !isNoneish(fill) + ? axisTextKx(k, anchor, data, { + fill, + fillOpacity, + stroke: textStroke, + strokeOpacity: textStrokeOpacity, + strokeWidth: textStrokeWidth, + textAnchor, + tickSize, + tickPadding, + tickRotate, + y, + marginTop, + marginRight, + marginBottom, + marginLeft, + ...options + }) + : null, + !isNoneish(fill) && label !== null + ? text( + [], + labelOptions({fill, fillOpacity, ...options}, function (data, facets, channels, scales, dimensions) { + const scale = scales[k]; + const {marginTop, marginRight, marginBottom, marginLeft} = (k === "x" && dimensions.inset) || dimensions; + const cla = labelAnchor ?? (scale.bandwidth ? "center" : "right"); + const clo = labelOffset ?? (anchor === "top" ? marginTop : marginBottom) - 3; + if (cla === "center") { + this.frameAnchor = anchor; + this.textAnchor = undefined; // middle + } else { + this.frameAnchor = `${anchor}-${cla}`; + this.textAnchor = cla === "right" ? "end" : "start"; + } + this.lineAnchor = anchor; + this.dy = anchor === "top" ? -clo : clo; + this.dx = cla === "right" ? marginRight - 3 : cla === "left" ? 3 - marginLeft : 0; + this.ariaLabel = `${k}-axis label`; + return { + facets: [[0]], + channels: {text: {value: [formatAxisLabel(k, scale, {anchor, label, labelAnchor: cla, labelArrow})]}} + }; + }) + ) + : null + ); +} + +function axisTickKy( + k, + anchor, + data, + { + strokeWidth = 1, + strokeLinecap = null, + strokeLinejoin = null, + facetAnchor = anchor + (k === "y" ? "-empty" : ""), + frameAnchor = anchor, + tickSize, + inset = 0, + insetLeft = inset, + insetRight = inset, + dx = 0, + y = k === "y" ? undefined : null, + ...options + } +) { + return axisMark(vectorY, k, anchor, `${k}-axis tick`, data, { + strokeWidth, + strokeLinecap, + strokeLinejoin, + facetAnchor, + frameAnchor, + y, + ...options, + dx: anchor === "left" ? +dx - offset + +insetLeft : +dx + offset - insetRight, + anchor: "start", + length: tickSize, + shape: anchor === "left" ? shapeTickLeft : shapeTickRight + }); +} + +function axisTickKx( + k, + anchor, + data, + { + strokeWidth = 1, + strokeLinecap = null, + strokeLinejoin = null, + facetAnchor = anchor + (k === "x" ? "-empty" : ""), + frameAnchor = anchor, + tickSize, + inset = 0, + insetTop = inset, + insetBottom = inset, + dy = 0, + x = k === "x" ? undefined : null, + ...options + } +) { + return axisMark(vectorX, k, anchor, `${k}-axis tick`, data, { + strokeWidth, + strokeLinejoin, + strokeLinecap, + facetAnchor, + frameAnchor, + x, + ...options, + dy: anchor === "bottom" ? +dy - offset - insetBottom : +dy + offset + +insetTop, + anchor: "start", + length: tickSize, + shape: anchor === "bottom" ? shapeTickBottom : shapeTickTop + }); +} + +function axisTextKy( + k, + anchor, + data, + { + facetAnchor = anchor + (k === "y" ? "-empty" : ""), + frameAnchor = anchor, + tickSize, + tickRotate = 0, + tickPadding = Math.max(3, 9 - tickSize) + (Math.abs(tickRotate) > 60 ? 4 * Math.cos(tickRotate * radians) : 0), + text, + textAnchor = Math.abs(tickRotate) > 60 ? "middle" : anchor === "left" ? "end" : "start", + lineAnchor = tickRotate > 60 ? "top" : tickRotate < -60 ? "bottom" : "middle", + fontVariant, + inset = 0, + insetLeft = inset, + insetRight = inset, + dx = 0, + y = k === "y" ? undefined : null, + ...options + } +) { + return axisMark( + textY, + k, + anchor, + `${k}-axis tick label`, + data, + { + facetAnchor, + frameAnchor, + text, + textAnchor, + lineAnchor, + fontVariant, + rotate: tickRotate, + y, + ...options, + dx: anchor === "left" ? +dx - tickSize - tickPadding + +insetLeft : +dx + +tickSize + +tickPadding - insetRight + }, + function (scale, data, ticks, tickFormat, channels) { + if (fontVariant === undefined) this.fontVariant = inferFontVariant(scale); + if (text === undefined) channels.text = inferTextChannel(scale, data, ticks, tickFormat, anchor); + } + ); +} + +function axisTextKx( + k, + anchor, + data, + { + facetAnchor = anchor + (k === "x" ? "-empty" : ""), + frameAnchor = anchor, + tickSize, + tickRotate = 0, + tickPadding = Math.max(3, 9 - tickSize) + (Math.abs(tickRotate) >= 10 ? 4 * Math.cos(tickRotate * radians) : 0), + text, + textAnchor = Math.abs(tickRotate) >= 10 ? ((tickRotate < 0) ^ (anchor === "bottom") ? "start" : "end") : "middle", + lineAnchor = Math.abs(tickRotate) >= 10 ? "middle" : anchor === "bottom" ? "top" : "bottom", + fontVariant, + inset = 0, + insetTop = inset, + insetBottom = inset, + dy = 0, + x = k === "x" ? undefined : null, + ...options + } +) { + return axisMark( + textX, + k, + anchor, + `${k}-axis tick label`, + data, + { + facetAnchor, + frameAnchor, + text: text === undefined ? null : text, + textAnchor, + lineAnchor, + fontVariant, + rotate: tickRotate, + x, + ...options, + dy: anchor === "bottom" ? +dy + +tickSize + +tickPadding - insetBottom : +dy - tickSize - tickPadding + +insetTop + }, + function (scale, data, ticks, tickFormat, channels) { + if (fontVariant === undefined) this.fontVariant = inferFontVariant(scale); + if (text === undefined) channels.text = inferTextChannel(scale, data, ticks, tickFormat, anchor); + } + ); +} + +function gridY() { + const [data, options] = maybeData(...arguments); + return gridKy("y", anchorY(options), data, options); +} + +function gridFy() { + const [data, options] = maybeData(...arguments); + return gridKy("fy", anchorFy(options), data, options); +} + +function gridX() { + const [data, options] = maybeData(...arguments); + return gridKx("x", anchorX(options), data, options); +} + +function gridFx() { + const [data, options] = maybeData(...arguments); + return gridKx("fx", anchorFx(options), data, options); +} + +function gridKy( + k, + anchor, + data, + { + y = k === "y" ? undefined : null, + x = null, + x1 = anchor === "left" ? x : null, + x2 = anchor === "right" ? x : null, + ...options + } +) { + return axisMark(ruleY, k, anchor, `${k}-grid`, data, {y, x1, x2, ...gridDefaults(options)}); +} + +function gridKx( + k, + anchor, + data, + { + x = k === "x" ? undefined : null, + y = null, + y1 = anchor === "top" ? y : null, + y2 = anchor === "bottom" ? y : null, + ...options + } +) { + return axisMark(ruleX, k, anchor, `${k}-grid`, data, {x, y1, y2, ...gridDefaults(options)}); +} + +function gridDefaults({ + color = "currentColor", + opacity = 0.1, + stroke = color, + strokeOpacity = opacity, + strokeWidth = 1, + ...options +}) { + return {stroke, strokeOpacity, strokeWidth, ...options}; +} + +function labelOptions( + { + fill, + fillOpacity, + fontFamily, + fontSize, + fontStyle, + fontVariant, + fontWeight, + monospace, + pointerEvents, + shapeRendering, + clip = false + }, + initializer +) { + // Only propagate these options if constant. + [, fill] = maybeColorChannel(fill); + [, fillOpacity] = maybeNumberChannel(fillOpacity); + return { + facet: "super", + x: null, + y: null, + fill, + fillOpacity, + fontFamily, + fontSize, + fontStyle, + fontVariant, + fontWeight, + monospace, + pointerEvents, + shapeRendering, + clip, + initializer + }; +} + +function axisMark(mark, k, anchor, ariaLabel, data, options, initialize) { + let channels; + + function axisInitializer(data, facets, _channels, scales, dimensions, context) { + const initializeFacets = data == null && (k === "fx" || k === "fy"); + const {[k]: scale} = scales; + if (!scale) throw new Error(`missing scale: ${k}`); + const domain = scale.domain(); + let {interval, ticks, tickFormat, tickSpacing = k === "x" ? 80 : 35} = options; + // For a scale with a temporal domain, also allow the ticks to be specified + // as a string which is promoted to a time interval. In the case of ordinal + // scales, the interval is interpreted as UTC. + if (typeof ticks === "string" && hasTemporalDomain(scale)) (interval = ticks), (ticks = undefined); + // The interval axis option is an alternative method of specifying ticks; + // for example, for a numeric scale, ticks = 5 means “about 5 ticks” whereas + // interval = 5 means “ticks every 5 units”. (This is not to be confused + // with the interval scale option, which affects the scale’s behavior!) + // Lastly use the tickSpacing option to infer the desired tick count. + if (ticks === undefined) ticks = maybeRangeInterval(interval, scale.type) ?? inferTickCount(scale, tickSpacing); + if (data == null) { + if (isIterable(ticks)) { + // Use explicit ticks, if specified. + data = arrayify(ticks); + } else if (isInterval(ticks)) { + // Use the tick interval, if specified. + data = inclusiveRange(ticks, ...d3.extent(domain)); + } else if (scale.interval) { + // If the scale interval is a standard time interval such as "day", we + // may be able to generalize the scale interval it to a larger aligned + // time interval to create the desired number of ticks. + let interval = scale.interval; + if (scale.ticks) { + const [min, max] = d3.extent(domain); + const n = (max - min) / interval[intervalDuration]; // current tick count + // We don’t explicitly check that given interval is a time interval; + // in that case the generalized interval will be undefined, just like + // a nonstandard interval. TODO Generalize integer intervals, too. + interval = generalizeTimeInterval(interval, n / ticks) ?? interval; + data = inclusiveRange(interval, min, max); + } else { + data = domain; + const n = data.length; // current tick count + interval = generalizeTimeInterval(interval, n / ticks) ?? interval; + if (interval !== scale.interval) data = inclusiveRange(interval, ...d3.extent(data)); + } + if (interval === scale.interval) { + // If we weren’t able to generalize the scale’s interval, compute the + // positive number n such that taking every nth value from the scale’s + // domain produces as close as possible to the desired number of + // ticks. For example, if the domain has 100 values and 5 ticks are + // desired, n = 20. + const n = Math.round(data.length / ticks); + if (n > 1) data = data.filter((d, i) => i % n === 0); + } + } else if (scale.ticks) { + data = scale.ticks(ticks); + } else { + // For ordinal scales, the domain will already be generated using the + // scale’s interval, if any. + data = domain; + } + if (!scale.ticks && data.length && data !== domain) { + // For ordinal scales, intersect the ticks with the scale domain since + // the scale is only defined on its domain. If all of the ticks are + // removed, then warn that the ticks and scale domain may be misaligned + // (e.g., "year" ticks and "4 weeks" interval). + const domainSet = new d3.InternSet(domain); + data = data.filter((d) => domainSet.has(d)); + if (!data.length) warn(`Warning: the ${k}-axis ticks appear to not align with the scale domain, resulting in no ticks. Try different ticks?`); // prettier-ignore + } + if (k === "y" || k === "x") { + facets = [range(data)]; + } else { + channels[k] = {scale: k, value: identity$1}; + } + } + initialize?.call(this, scale, data, ticks, tickFormat, channels); + const initializedChannels = Object.fromEntries( + Object.entries(channels).map(([name, channel]) => { + return [name, {...channel, value: valueof(data, channel.value)}]; + }) + ); + if (initializeFacets) facets = context.filterFacets(data, initializedChannels); + return {data, facets, channels: initializedChannels}; + } + + // Apply any basic initializers after the axis initializer computes the ticks. + const basicInitializer = initializer(options).initializer; + const m = mark(data, initializer({...options, initializer: axisInitializer}, basicInitializer)); + if (data == null) { + channels = m.channels; + m.channels = {}; + } else { + channels = {}; + } + m.ariaLabel = ariaLabel; + if (m.clip === undefined) m.clip = false; // don’t clip axes by default + return m; +} + +function inferTickCount(scale, tickSpacing) { + const [min, max] = d3.extent(scale.range()); + return (max - min) / tickSpacing; +} + +function inferTextChannel(scale, data, ticks, tickFormat, anchor) { + return {value: inferTickFormat(scale, data, ticks, tickFormat, anchor)}; +} + +// D3’s ordinal scales simply use toString by default, but if the ordinal scale +// domain (or ticks) are numbers or dates (say because we’re applying a time +// interval to the ordinal scale), we want Plot’s default formatter. And for +// time ticks, we want to use the multi-line time format (e.g., Jan 26) if +// possible, or the default ISO format (2014-01-26). TODO We need a better way +// to infer whether the ordinal scale is UTC or local time. +function inferTickFormat(scale, data, ticks, tickFormat, anchor) { + return typeof tickFormat === "function" + ? tickFormat + : tickFormat === undefined && data && isTemporal(data) + ? inferTimeFormat(scale.type, data, anchor) ?? formatDefault + : scale.tickFormat + ? scale.tickFormat(typeof ticks === "number" ? ticks : null, tickFormat) + : tickFormat === undefined + ? formatDefault + : typeof tickFormat === "string" + ? (isTemporal(scale.domain()) ? d3.utcFormat : d3.format)(tickFormat) + : constant(tickFormat); +} + +function inclusiveRange(interval, min, max) { + return interval.range(min, interval.offset(interval.floor(max))); +} + +const shapeTickBottom = { + draw(context, l) { + context.moveTo(0, 0); + context.lineTo(0, l); + } +}; + +const shapeTickTop = { + draw(context, l) { + context.moveTo(0, 0); + context.lineTo(0, -l); + } +}; + +const shapeTickLeft = { + draw(context, l) { + context.moveTo(0, 0); + context.lineTo(-l, 0); + } +}; + +const shapeTickRight = { + draw(context, l) { + context.moveTo(0, 0); + context.lineTo(l, 0); + } +}; + +// TODO Unify this with the other inferFontVariant; here we only have a scale +// function rather than a scale descriptor. +function inferFontVariant(scale) { + return scale.bandwidth && !scale.interval ? undefined : "tabular-nums"; +} + +// Takes the scale label, and if this is not an ordinal scale and the label was +// inferred from an associated channel, adds an orientation-appropriate arrow. +function formatAxisLabel(k, scale, {anchor, label = scale.label, labelAnchor, labelArrow} = {}) { + if (label == null || (label.inferred && hasTemporalDomain(scale) && /^(date|time|year)$/i.test(label))) return; + label = String(label); // coerce to a string after checking if inferred + if (labelArrow === "auto") labelArrow = (!scale.bandwidth || scale.interval) && !/[↑↓→←]/.test(label); + if (!labelArrow) return label; + if (labelArrow === true) { + const order = inferScaleOrder(scale); + if (order) + labelArrow = + /x$/.test(k) || labelAnchor === "center" + ? /x$/.test(k) === order < 0 + ? "left" + : "right" + : order < 0 + ? "up" + : "down"; + } + switch (labelArrow) { + case "left": + return `← ${label}`; + case "right": + return `${label} →`; + case "up": + return anchor === "right" ? `${label} ↑` : `↑ ${label}`; + case "down": + return anchor === "right" ? `${label} ↓` : `↓ ${label}`; + } + return label; +} + +function maybeLabelArrow(labelArrow = "auto") { + return isNoneish(labelArrow) + ? false + : typeof labelArrow === "boolean" + ? labelArrow + : keyword(labelArrow, "labelArrow", ["auto", "up", "right", "down", "left"]); +} + +function hasTemporalDomain(scale) { + return isTemporal(scale.domain()); +} + +function maybeScale(scale, key) { + if (key == null) return key; + const s = scale(key); + if (!s) throw new Error(`scale not found: ${key}`); + return s; +} + +function legendSwatches(color, {opacity, ...options} = {}) { + if (!isOrdinalScale(color) && !isThresholdScale(color)) + throw new Error(`swatches legend requires ordinal or threshold color scale (not ${color.type})`); + return legendItems(color, options, (selection, scale, width, height) => + selection + .append("svg") + .attr("width", width) + .attr("height", height) + .attr("fill", scale.scale) + .attr("fill-opacity", maybeNumberChannel(opacity)[1]) + .append("rect") + .attr("width", "100%") + .attr("height", "100%") + ); +} + +function legendSymbols( + symbol, + { + fill = symbol.hint?.fill !== undefined ? symbol.hint.fill : "none", + fillOpacity = 1, + stroke = symbol.hint?.stroke !== undefined ? symbol.hint.stroke : isNoneish(fill) ? "currentColor" : "none", + strokeOpacity = 1, + strokeWidth = 1.5, + r = 4.5, + ...options + } = {}, + scale +) { + const [vf, cf] = maybeColorChannel(fill); + const [vs, cs] = maybeColorChannel(stroke); + const sf = maybeScale(scale, vf); + const ss = maybeScale(scale, vs); + const size = r * r * Math.PI; + fillOpacity = maybeNumberChannel(fillOpacity)[1]; + strokeOpacity = maybeNumberChannel(strokeOpacity)[1]; + strokeWidth = maybeNumberChannel(strokeWidth)[1]; + return legendItems(symbol, options, (selection, scale, width, height) => + selection + .append("svg") + .attr("viewBox", "-8 -8 16 16") + .attr("width", width) + .attr("height", height) + .attr("fill", vf === "color" ? (d) => sf.scale(d) : cf) + .attr("fill-opacity", fillOpacity) + .attr("stroke", vs === "color" ? (d) => ss.scale(d) : cs) + .attr("stroke-opacity", strokeOpacity) + .attr("stroke-width", strokeWidth) + .append("path") + .attr("d", (d) => { + const p = d3.pathRound(); + symbol.scale(d).draw(p, size); + return p; + }) + ); +} + +function legendItems(scale, options = {}, swatch) { + let { + columns, + tickFormat, + fontVariant = inferFontVariant$2(scale), + // TODO label, + swatchSize = 15, + swatchWidth = swatchSize, + swatchHeight = swatchSize, + marginLeft = 0, + className, + style, + width + } = options; + const context = createContext(options); + className = maybeClassName(className); + tickFormat = inferTickFormat(scale.scale, scale.domain, undefined, tickFormat); + + const swatches = create("div", context).attr( + "class", + `${className}-swatches ${className}-swatches-${columns != null ? "columns" : "wrap"}` + ); + + let extraStyle; + + if (columns != null) { + extraStyle = `.${className}-swatches-columns .${className}-swatch { + display: flex; + align-items: center; + break-inside: avoid; + padding-bottom: 1px; +} +.${className}-swatches-columns .${className}-swatch::before { + flex-shrink: 0; +} +.${className}-swatches-columns .${className}-swatch-label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +}`; + + swatches + .style("columns", columns) + .selectAll() + .data(scale.domain) + .enter() + .append("div") + .attr("class", `${className}-swatch`) + .call(swatch, scale, swatchWidth, swatchHeight) + .call((item) => + item.append("div").attr("class", `${className}-swatch-label`).attr("title", tickFormat).text(tickFormat) + ); + } else { + extraStyle = `.${className}-swatches-wrap { + display: flex; + align-items: center; + min-height: 33px; + flex-wrap: wrap; +} +.${className}-swatches-wrap .${className}-swatch { + display: inline-flex; + align-items: center; + margin-right: 1em; +}`; + + swatches + .selectAll() + .data(scale.domain) + .enter() + .append("span") + .attr("class", `${className}-swatch`) + .call(swatch, scale, swatchWidth, swatchHeight) + .append(function () { + return this.ownerDocument.createTextNode(tickFormat.apply(this, arguments)); + }); + } + + return swatches + .call((div) => + div.insert("style", "*").text( + `.${className}-swatches { + font-family: system-ui, sans-serif; + font-size: 10px; + margin-bottom: 0.5em; +} +.${className}-swatch > svg { + margin-right: 0.5em; + overflow: visible; +} +${extraStyle}` + ) + ) + .style("margin-left", marginLeft ? `${+marginLeft}px` : null) + .style("width", width === undefined ? null : `${+width}px`) + .style("font-variant", impliedString(fontVariant, "normal")) + .call(applyInlineStyles, style) + .node(); +} + +const legendRegistry = new Map([ + ["symbol", legendSymbols], + ["color", legendColor], + ["opacity", legendOpacity] +]); + +function legend(options = {}) { + for (const [key, value] of legendRegistry) { + const scale = options[key]; + if (isScaleOptions(scale)) { + // e.g., ignore {color: "red"} + const context = createContext(options); + let hint; + // For symbol legends, pass a hint to the symbol scale. + if (key === "symbol") { + const {fill, stroke = fill === undefined && isScaleOptions(options.color) ? "color" : undefined} = options; + hint = {fill, stroke}; + } + return value(normalizeScale(key, scale, hint), legendOptions(context, scale, options), (key) => + isScaleOptions(options[key]) ? normalizeScale(key, options[key]) : null + ); + } + } + throw new Error("unknown legend type; no scale found"); +} + +function exposeLegends(scales, context, defaults = {}) { + return (key, options) => { + if (!legendRegistry.has(key)) throw new Error(`unknown legend type: ${key}`); + if (!(key in scales)) return; + return legendRegistry.get(key)(scales[key], legendOptions(context, defaults[key], options), (key) => scales[key]); + }; +} + +function legendOptions({className, ...context}, {label, ticks, tickFormat} = {}, options) { + return inherit(options, {className, ...context}, {label, ticks, tickFormat}); +} + +function legendColor(color, {legend = true, ...options}) { + if (legend === true) legend = color.type === "ordinal" ? "swatches" : "ramp"; + if (color.domain === undefined) return; + switch (`${legend}`.toLowerCase()) { + case "swatches": + return legendSwatches(color, options); + case "ramp": + return legendRamp(color, options); + default: + throw new Error(`unknown legend type: ${legend}`); + } +} + +function legendOpacity({type, interpolate, ...scale}, {legend = true, color = d3.rgb(0, 0, 0), ...options}) { + if (!interpolate) throw new Error(`${type} opacity scales are not supported`); + if (legend === true) legend = "ramp"; + if (`${legend}`.toLowerCase() !== "ramp") throw new Error(`${legend} opacity legends are not supported`); + return legendColor({type, ...scale, interpolate: interpolateOpacity(color)}, {legend, ...options}); +} + +function interpolateOpacity(color) { + const {r, g, b} = d3.rgb(color) || d3.rgb(0, 0, 0); // treat invalid color as black + return (t) => `rgba(${r},${g},${b},${t})`; +} + +function createLegends(scales, context, options) { + const legends = []; + for (const [key, value] of legendRegistry) { + const o = options[key]; + if (o?.legend && key in scales) { + const legend = value(scales[key], legendOptions(context, scales[key], o), (key) => scales[key]); + if (legend != null) legends.push(legend); + } + } + return legends; +} + +const defaults$i = { + ariaLabel: "frame", + fill: "none", + stroke: "currentColor", + clip: false +}; + +const lineDefaults = { + ariaLabel: "frame", + fill: null, + stroke: "currentColor", + strokeLinecap: "square", + clip: false +}; + +class Frame extends Mark { + constructor(options = {}) { + const { + anchor = null, + inset = 0, + insetTop = inset, + insetRight = inset, + insetBottom = inset, + insetLeft = inset, + rx, + ry + } = options; + super(singleton, undefined, options, anchor == null ? defaults$i : lineDefaults); + this.anchor = maybeKeyword(anchor, "anchor", ["top", "right", "bottom", "left"]); + this.insetTop = number$1(insetTop); + this.insetRight = number$1(insetRight); + this.insetBottom = number$1(insetBottom); + this.insetLeft = number$1(insetLeft); + this.rx = number$1(rx); + this.ry = number$1(ry); + } + render(index, scales, channels, dimensions, context) { + const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions; + const {anchor, insetTop, insetRight, insetBottom, insetLeft, rx, ry} = this; + const x1 = marginLeft + insetLeft; + const x2 = width - marginRight - insetRight; + const y1 = marginTop + insetTop; + const y2 = height - marginBottom - insetBottom; + return create(anchor ? "svg:line" : "svg:rect", context) + .datum(0) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyDirectStyles, this) + .call(applyChannelStyles, this, channels) + .call(applyTransform, this, {}) + .call( + anchor === "left" + ? (line) => line.attr("x1", x1).attr("x2", x1).attr("y1", y1).attr("y2", y2) + : anchor === "right" + ? (line) => line.attr("x1", x2).attr("x2", x2).attr("y1", y1).attr("y2", y2) + : anchor === "top" + ? (line) => line.attr("x1", x1).attr("x2", x2).attr("y1", y1).attr("y2", y1) + : anchor === "bottom" + ? (line) => line.attr("x1", x1).attr("x2", x2).attr("y1", y2).attr("y2", y2) + : (rect) => + rect + .attr("x", x1) + .attr("y", y1) + .attr("width", x2 - x1) + .attr("height", y2 - y1) + .attr("rx", rx) + .attr("ry", ry) + ) + .node(); + } +} + +function frame(options) { + return new Frame(options); +} + +const defaults$h = { + ariaLabel: "tip", + fill: "var(--plot-background)", + stroke: "currentColor" +}; + +// These channels are not displayed in the default tip; see formatChannels. +const ignoreChannels = new Set(["geometry", "href", "src", "ariaLabel", "scales"]); + +class Tip extends Mark { + constructor(data, options = {}) { + if (options.tip) options = {...options, tip: false}; + if (options.title === undefined && isIterable(data) && isTextual(data)) options = {...options, title: identity$1}; + const { + x, + y, + x1, + x2, + y1, + y2, + anchor, + preferredAnchor = "bottom", + monospace, + fontFamily = monospace ? "ui-monospace, monospace" : undefined, + fontSize, + fontStyle, + fontVariant, + fontWeight, + lineHeight = 1, + lineWidth = 20, + frameAnchor, + format, + textAnchor = "start", + textOverflow, + textPadding = 8, + title, + pointerSize = 12, + pathFilter = "drop-shadow(0 3px 4px rgba(0,0,0,0.2))" + } = options; + super( + data, + { + x: {value: x1 != null && x2 != null ? null : x, scale: "x", optional: true}, // ignore midpoint + y: {value: y1 != null && y2 != null ? null : y, scale: "y", optional: true}, // ignore midpoint + x1: {value: x1, scale: "x", optional: x2 == null}, + y1: {value: y1, scale: "y", optional: y2 == null}, + x2: {value: x2, scale: "x", optional: x1 == null}, + y2: {value: y2, scale: "y", optional: y1 == null}, + title: {value: title, optional: true} // filter: defined + }, + options, + defaults$h + ); + this.anchor = maybeAnchor$3(anchor, "anchor"); + this.preferredAnchor = maybeAnchor$3(preferredAnchor, "preferredAnchor"); + this.frameAnchor = maybeFrameAnchor(frameAnchor); + this.textAnchor = impliedString(textAnchor, "middle"); + this.textPadding = +textPadding; + this.pointerSize = +pointerSize; + this.pathFilter = string(pathFilter); + this.lineHeight = +lineHeight; + this.lineWidth = +lineWidth; + this.textOverflow = maybeTextOverflow(textOverflow); + this.monospace = !!monospace; + this.fontFamily = string(fontFamily); + this.fontSize = number$1(fontSize); + this.fontStyle = string(fontStyle); + this.fontVariant = string(fontVariant); + this.fontWeight = string(fontWeight); + for (const key in defaults$h) if (key in this.channels) this[key] = defaults$h[key]; // apply default even if channel + this.splitLines = splitter(this); + this.clipLine = clipper(this); + this.format = {...format}; // defensive copy before mutate; also promote nullish to empty + } + render(index, scales, values, dimensions, context) { + const mark = this; + const {x, y, fx, fy} = scales; + const {ownerSVGElement: svg, document} = context; + const {anchor, monospace, lineHeight, lineWidth} = this; + const {textPadding: r, pointerSize: m, pathFilter} = this; + const {marginTop, marginLeft} = dimensions; + + // The anchor position is the middle of x1 & y1 and x2 & y2, if available, + // or x & y; the former is considered more specific because it’s how we + // disable the implicit stack and interval transforms. If any dimension is + // unspecified, we fallback to the frame anchor. We also need to know the + // facet offsets to detect when the tip would draw outside the plot, and + // thus we need to change the orientation. + const {x1: X1, y1: Y1, x2: X2, y2: Y2, x: X = X1 ?? X2, y: Y = Y1 ?? Y2} = values; + const ox = fx ? fx(index.fx) - marginLeft : 0; + const oy = fy ? fy(index.fy) - marginTop : 0; + + // The order of precedence for the anchor position is: the middle of x1 & y1 + // and x2 & y2; or x1 & y1 (e.g., area); or lastly x & y. If a dimension is + // unspecified, the frame anchor is used. + const [cx, cy] = applyFrameAnchor(this, dimensions); + const px = anchorX$1(values, cx); + const py = anchorY$1(values, cy); + + // Resolve the text metric implementation. We may need an ellipsis for text + // truncation, so we optimistically compute the ellipsis width. + const widthof = monospace ? monospaceWidth : defaultWidth; + const ee = widthof(ellipsis); + + // If there’s a title channel, display that as-is; otherwise, show multiple + // channels as name-value pairs. + let sources, format; + if ("title" in values) { + sources = values.channels; + format = formatTitle; + } else { + sources = getSourceChannels.call(this, values, scales); + format = formatChannels; + } + + // We don’t call applyChannelStyles because we only use the channels to + // derive the content of the tip, not its aesthetics. + const g = create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyIndirectTextStyles, this) + .call(applyTransform, this, {x: X && x, y: Y && y}) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("g") + .attr("transform", (i) => `translate(${Math.round(px(i))},${Math.round(py(i))})`) // crisp edges + .call(applyDirectStyles, this) + .call((g) => g.append("path").attr("filter", pathFilter)) + .call((g) => + g.append("text").each(function (i) { + const that = d3.select(this); + // prevent style inheritance (from path) + this.setAttribute("fill", "currentColor"); + this.setAttribute("fill-opacity", 1); + this.setAttribute("stroke", "none"); + // iteratively render each channel value + const lines = format.call(mark, i, index, sources, scales, values); + if (typeof lines === "string") { + for (const line of mark.splitLines(lines)) { + renderLine(that, {value: mark.clipLine(line)}); + } + } else { + const labels = new Set(); + for (const line of lines) { + const {label = ""} = line; + if (label && labels.has(label)) continue; + else labels.add(label); + renderLine(that, line); + } + } + }) + ) + ); + + // Renders a single line (a name-value pair) to the tip, truncating the text + // as needed, and adding a title if the text is truncated. Note that this is + // just the initial layout of the text; in postrender we will compute the + // exact text metrics and translate the text as needed once we know the + // tip’s orientation (anchor). + function renderLine(selection, {label, value, color, opacity}) { + (label ??= ""), (value ??= ""); + const swatch = color != null || opacity != null; + let title; + let w = lineWidth * 100; + const [j] = cut(label, w, widthof, ee); + if (j >= 0) { + // label is truncated + label = label.slice(0, j).trimEnd() + ellipsis; + title = value.trim(); + value = ""; + } else { + if (label || (!value && !swatch)) value = " " + value; + const [k] = cut(value, w - widthof(label), widthof, ee); + if (k >= 0) { + // value is truncated + title = value.trim(); + value = value.slice(0, k).trimEnd() + ellipsis; + } + } + const line = selection.append("tspan").attr("x", 0).attr("dy", `${lineHeight}em`).text("\u200b"); // zwsp for double-click + if (label) line.append("tspan").attr("font-weight", "bold").text(label); + if (value) line.append(() => document.createTextNode(value)); + if (swatch) line.append("tspan").text(" ■").attr("fill", color).attr("fill-opacity", opacity).style("user-select", "none"); // prettier-ignore + if (title) line.append("title").text(title); + } + + // Only after the plot is attached to the page can we compute the exact text + // metrics needed to determine the tip size and orientation (anchor). + function postrender() { + const {width, height} = dimensions.facet ?? dimensions; + g.selectChildren().each(function (i) { + let {x: tx, width: w, height: h} = this.getBBox(); + (w = Math.round(w)), (h = Math.round(h)); // crisp edges + let a = anchor; // use the specified anchor, if any + if (a === undefined) { + const x = px(i) + ox; + const y = py(i) + oy; + const fitLeft = x + w + m + r * 2 < width; + const fitRight = x - w - m - r * 2 > 0; + const fitTop = y + h + m + r * 2 < height; + const fitBottom = y - h - m - r * 2 > 0; + a = + fitLeft && fitRight + ? fitTop && fitBottom + ? mark.preferredAnchor + : fitBottom + ? "bottom" + : "top" + : fitTop && fitBottom + ? fitLeft + ? "left" + : "right" + : (fitLeft || fitRight) && (fitTop || fitBottom) + ? `${fitBottom ? "bottom" : "top"}-${fitLeft ? "left" : "right"}` + : mark.preferredAnchor; + } + const path = this.firstChild; // note: assumes exactly two children! + const text = this.lastChild; // note: assumes exactly two children! + path.setAttribute("d", getPath(a, m, r, w, h)); + if (tx) for (const t of text.childNodes) t.setAttribute("x", -tx); + text.setAttribute("y", `${+getLineOffset(a, text.childNodes.length, lineHeight).toFixed(6)}em`); + text.setAttribute("transform", `translate(${getTextTranslate(a, m, r, w, h)})`); + }); + g.attr("visibility", null); + } + + // Wait until the plot is inserted into the page so that we can use getBBox + // to compute the exact text dimensions. If the SVG is already connected, as + // when the pointer interaction triggers the re-render, use a faster + // microtask instead of an animation frame; if this SSR (e.g., JSDOM), skip + // this step. Perhaps this could be done synchronously; getting the + // dimensions of the SVG is easy, and although accurate text metrics are + // hard, we could use approximate heuristics. + if (index.length) { + g.attr("visibility", "hidden"); // hide until postrender + if (svg.isConnected) Promise.resolve().then(postrender); + else if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(postrender); + } + + return g.node(); + } +} + +function tip(data, {x, y, ...options} = {}) { + if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); + return new Tip(data, {...options, x, y}); +} + +function getLineOffset(anchor, length, lineHeight) { + return /^top(?:-|$)/.test(anchor) + ? 0.94 - lineHeight + : /^bottom(?:-|$)/ + ? -0.29 - length * lineHeight + : (length / 2) * lineHeight; +} + +function getTextTranslate(anchor, m, r, width, height) { + switch (anchor) { + case "middle": + return [-width / 2, height / 2]; + case "top-left": + return [r, m + r]; + case "top": + return [-width / 2, m / 2 + r]; + case "top-right": + return [-width - r, m + r]; + case "right": + return [-m / 2 - width - r, height / 2]; + case "bottom-left": + return [r, -m - r]; + case "bottom": + return [-width / 2, -m / 2 - r]; + case "bottom-right": + return [-width - r, -m - r]; + case "left": + return [r + m / 2, height / 2]; + } +} + +function getPath(anchor, m, r, width, height) { + const w = width + r * 2; + const h = height + r * 2; + switch (anchor) { + case "middle": + return `M${-w / 2},${-h / 2}h${w}v${h}h${-w}z`; + case "top-left": + return `M0,0l${m},${m}h${w - m}v${h}h${-w}z`; + case "top": + return `M0,0l${m / 2},${m / 2}h${(w - m) / 2}v${h}h${-w}v${-h}h${(w - m) / 2}z`; + case "top-right": + return `M0,0l${-m},${m}h${m - w}v${h}h${w}z`; + case "right": + return `M0,0l${-m / 2},${-m / 2}v${m / 2 - h / 2}h${-w}v${h}h${w}v${m / 2 - h / 2}z`; + case "bottom-left": + return `M0,0l${m},${-m}h${w - m}v${-h}h${-w}z`; + case "bottom": + return `M0,0l${m / 2},${-m / 2}h${(w - m) / 2}v${-h}h${-w}v${h}h${(w - m) / 2}z`; + case "bottom-right": + return `M0,0l${-m},${-m}h${m - w}v${-h}h${w}z`; + case "left": + return `M0,0l${m / 2},${-m / 2}v${m / 2 - h / 2}h${w}v${h}h${-w}v${m / 2 - h / 2}z`; + } +} + +// Note: mutates this.format! +function getSourceChannels({channels}, scales) { + const sources = {}; + + // Promote x and y shorthand for paired channels (in order). + let format = this.format; + format = maybeExpandPairedFormat(format, channels, "x"); + format = maybeExpandPairedFormat(format, channels, "y"); + this.format = format; + + // Prioritize channels with explicit formats, in the given order. + for (const key in format) { + const value = format[key]; + if (value === null || value === false) { + continue; + } else if (key === "fx" || key === "fy") { + sources[key] = true; + } else { + const source = getSource(channels, key); + if (source) sources[key] = source; + } + } + + // Then fallback to all other (non-ignored) channels. + for (const key in channels) { + if (key in sources || key in format || ignoreChannels.has(key)) continue; + const source = getSource(channels, key); + if (source) sources[key] = source; + } + + // And lastly facet channels, but only if this mark is faceted. + if (this.facet) { + if (scales.fx && !("fx" in format)) sources.fx = true; + if (scales.fy && !("fy" in format)) sources.fy = true; + } + + // Promote shorthand string formats, and materialize default formats. + for (const key in sources) { + const format = this.format[key]; + if (typeof format === "string") { + const value = sources[key]?.value ?? scales[key]?.domain() ?? []; + this.format[key] = (isTemporal(value) ? d3.utcFormat : d3.format)(format); + } else if (format === undefined || format === true) { + // For ordinal scales, the inferred tick format can be more concise, such + // as only showing the year for yearly data. + const scale = scales[key]; + this.format[key] = scale?.bandwidth ? inferTickFormat(scale, scale.domain()) : formatDefault; + } + } + + return sources; +} + +// Promote x and y shorthand for paired channels, while preserving order. +function maybeExpandPairedFormat(format, channels, key) { + if (!(key in format)) return format; + const key1 = `${key}1`; + const key2 = `${key}2`; + if ((key1 in format || !(key1 in channels)) && (key2 in format || !(key2 in channels))) return format; + const entries = Object.entries(format); + const value = format[key]; + entries.splice(entries.findIndex(([name]) => name === key) + 1, 0, [key1, value], [key2, value]); + return Object.fromEntries(entries); +} + +function formatTitle(i, index, {title}) { + return formatDefault(title.value[i], i); +} + +function* formatChannels(i, index, channels, scales, values) { + for (const key in channels) { + if (key === "fx" || key === "fy") { + yield { + label: formatLabel(scales, channels, key), + value: this.format[key](index[key], i) + }; + continue; + } + if (key === "x1" && "x2" in channels) continue; + if (key === "y1" && "y2" in channels) continue; + const channel = channels[key]; + if (key === "x2" && "x1" in channels) { + yield { + label: formatPairLabel(scales, channels, "x"), + value: formatPair(this.format.x2, channels.x1, channel, i) + }; + } else if (key === "y2" && "y1" in channels) { + yield { + label: formatPairLabel(scales, channels, "y"), + value: formatPair(this.format.y2, channels.y1, channel, i) + }; + } else { + const value = channel.value[i]; + const scale = channel.scale; + if (!defined(value) && scale == null) continue; + yield { + label: formatLabel(scales, channels, key), + value: this.format[key](value, i), + color: scale === "color" ? values[key][i] : null, + opacity: scale === "opacity" ? values[key][i] : null + }; + } + } +} + +function formatPair(formatValue, c1, c2, i) { + return c2.hint?.length // e.g., stackY’s y1 and y2 + ? `${formatValue(c2.value[i] - c1.value[i], i)}` + : `${formatValue(c1.value[i], i)}–${formatValue(c2.value[i], i)}`; +} + +function formatPairLabel(scales, channels, key) { + const l1 = formatLabel(scales, channels, `${key}1`, key); + const l2 = formatLabel(scales, channels, `${key}2`, key); + return l1 === l2 ? l1 : `${l1}–${l2}`; +} + +function formatLabel(scales, channels, key, defaultLabel = key) { + const channel = channels[key]; + const scale = scales[channel?.scale ?? key]; + return String(scale?.label ?? channel?.label ?? defaultLabel); +} + +function plot(options = {}) { + const {facet, style, title, subtitle, caption, ariaLabel, ariaDescription} = options; + + // className for inline styles + const className = maybeClassName(options.className); + + // Flatten any nested marks. + const marks = options.marks === undefined ? [] : flatMarks(options.marks); + + // Add implicit tips. + marks.push(...inferTips(marks)); + + // Compute the top-level facet state. This has roughly the same structure as + // mark-specific facet state, except there isn’t a facetsIndex, and there’s a + // data and dataLength so we can warn the user if a different data of the same + // length is used in a mark. + const topFacetState = maybeTopFacet(facet, options); + + // Construct a map from (faceted) Mark instance to facet state, including: + // channels - an {fx?, fy?} object to add to the fx and fy scale + // groups - a possibly-nested map from facet values to indexes in the data array + // facetsIndex - a sparse nested array of indices corresponding to the valid facets + const facetStateByMark = new Map(); + for (const mark of marks) { + const facetState = maybeMarkFacet(mark, topFacetState, options); + if (facetState) facetStateByMark.set(mark, facetState); + } + + // Compute a Map from scale name to an array of associated channels. + const channelsByScale = new Map(); + if (topFacetState) addScaleChannels(channelsByScale, [topFacetState], options); + addScaleChannels(channelsByScale, facetStateByMark, options); + + // Add implicit axis marks. Because this happens after faceting (because it + // depends on whether faceting is present), we must initialize the facet state + // of any implicit axes, too. + const axes = flatMarks(inferAxes(marks, channelsByScale, options)); + for (const mark of axes) { + const facetState = maybeMarkFacet(mark, topFacetState, options); + if (facetState) facetStateByMark.set(mark, facetState); + } + marks.unshift(...axes); + + // All the possible facets are given by the domains of the fx or fy scales, or + // the cross-product of these domains if we facet by both x and y. We sort + // them in order to apply the facet filters afterwards. + let facets = createFacets(channelsByScale, options); + + if (facets !== undefined) { + const topFacetsIndex = topFacetState ? facetFilter(facets, topFacetState) : undefined; + + // Compute a facet index for each mark, parallel to the facets array. For + // mark-level facets, compute an index for that mark’s data and options. + // Otherwise, use the top-level facet index. + for (const mark of marks) { + if (mark.facet === null || mark.facet === "super") continue; + const facetState = facetStateByMark.get(mark); + if (facetState === undefined) continue; + facetState.facetsIndex = mark.fx != null || mark.fy != null ? facetFilter(facets, facetState) : topFacetsIndex; + } + + // The cross product of the domains of fx and fy can include fx-fy + // combinations for which no mark has an instance associated with that + // combination, and therefore we don’t want to render this facet (not even + // the frame). The same can occur if you specify the domain of fx and fy + // explicitly, but there is no mark instance associated with some values in + // the domain. Expunge empty facets, and clear the corresponding elements + // from the nested index in each mark. + const nonEmpty = new Set(); + for (const {facetsIndex} of facetStateByMark.values()) { + facetsIndex?.forEach((index, i) => { + if (index?.length > 0) { + nonEmpty.add(i); + } + }); + } + + // If all the facets are empty (as when none of the marks are actually + // faceted), none of them are empty. + facets.forEach( + 0 < nonEmpty.size && nonEmpty.size < facets.length + ? (f, i) => (f.empty = !nonEmpty.has(i)) + : (f) => (f.empty = false) + ); + + // For any mark using the “exclude” facet mode, invert the index. + for (const mark of marks) { + if (mark.facet === "exclude") { + const facetState = facetStateByMark.get(mark); + if (facetState !== undefined) facetState.facetsIndex = facetExclude(facetState.facetsIndex); + } + } + } + + // If a scale is explicitly declared in options, initialize its associated + // channels to the empty array; this will guarantee that a corresponding scale + // will be created later (even if there are no other channels). Ignore facet + // scale declarations, which are handled above. + for (const key of registry.keys()) { + if (isScaleOptions(options[key]) && key !== "fx" && key !== "fy") { + channelsByScale.set(key, []); + } + } + + // A Map from Mark instance to its render state, including: + // index - the data index e.g. [0, 1, 2, 3, …] + // channels - an array of materialized channels e.g. [["x", {value}], …] + // faceted - a boolean indicating whether this mark is faceted + // values - an object of scaled values e.g. {x: [40, 32, …], …} + const stateByMark = new Map(); + + // Initialize the marks’ state. + for (const mark of marks) { + if (stateByMark.has(mark)) throw new Error("duplicate mark; each mark must be unique"); + const {facetsIndex, channels: facetChannels} = facetStateByMark.get(mark) ?? {}; + const {data, facets, channels} = mark.initialize(facetsIndex, facetChannels, options); + applyScaleTransforms(channels, options); + stateByMark.set(mark, {data, facets, channels}); + } + + // Initalize the scales and dimensions. + const scaleDescriptors = createScales(addScaleChannels(channelsByScale, stateByMark, options), options); + const dimensions = createDimensions(scaleDescriptors, marks, options); + + autoScaleRange(scaleDescriptors, dimensions); + + const scales = createScaleFunctions(scaleDescriptors); + const {fx, fy} = scales; + const subdimensions = fx || fy ? innerDimensions(scaleDescriptors, dimensions) : dimensions; + const superdimensions = fx || fy ? actualDimensions(scales, dimensions) : dimensions; + + // Initialize the context. + const context = createContext(options); + const document = context.document; + const svg = d3.creator("svg").call(document.documentElement); + let figure = svg; // replaced with the figure element, if any + context.ownerSVGElement = svg; + context.className = className; + context.projection = createProjection(options, subdimensions); + + // Allows e.g. the axis mark to determine faceting lazily. + context.filterFacets = (data, channels) => { + return facetFilter(facets, {channels, groups: facetGroups(data, channels)}); + }; + + // Allows e.g. the tip mark to reference channels and data on other marks. + context.getMarkState = (mark) => { + const state = stateByMark.get(mark); + const facetState = facetStateByMark.get(mark); + return {...state, channels: {...state.channels, ...facetState?.channels}}; + }; + + // Allows e.g. the pointer transform to support viewof. + context.dispatchValue = (value) => { + if (figure.value === value) return; + figure.value = value; + figure.dispatchEvent(new Event("input", {bubbles: true})); + }; + + // Reinitialize; for deriving channels dependent on other channels. + const newByScale = new Set(); + for (const [mark, state] of stateByMark) { + if (mark.initializer != null) { + const dimensions = mark.facet === "super" ? superdimensions : subdimensions; + const update = mark.initializer(state.data, state.facets, state.channels, scales, dimensions, context); + if (update.data !== undefined) { + state.data = update.data; + } + if (update.facets !== undefined) { + state.facets = update.facets; + } + if (update.channels !== undefined) { + const {fx, fy, ...channels} = update.channels; // separate facet channels + inferChannelScales(channels); + Object.assign(state.channels, channels); + for (const channel of Object.values(channels)) { + const {scale} = channel; + // Initializers aren’t allowed to redefine position scales as this + // would introduce a circular dependency; so simply scale these + // channels as-is rather than creating new scales, and assume that + // they already have the scale’s transform applied, if any (e.g., when + // generating ticks for the axis mark). + if (scale != null && !isPosition(registry.get(scale))) { + applyScaleTransform(channel, options); + newByScale.add(scale); + } + } + // If the initializer returns new mark-level facet channels, we must + // record that the mark is now faceted. Note: we aren’t actually + // populating the facet state, but subsequently we won’t need it. + if (fx != null || fy != null) facetStateByMark.set(mark, true); + } + } + } + + // Reconstruct scales if new scaled channels were created during + // reinitialization. Preserve existing scale labels, if any. + if (newByScale.size) { + const newChannelsByScale = new Map(); + addScaleChannels(newChannelsByScale, stateByMark, options, (key) => newByScale.has(key)); + addScaleChannels(channelsByScale, stateByMark, options, (key) => newByScale.has(key)); + const newScaleDescriptors = inheritScaleLabels(createScales(newChannelsByScale, options), scaleDescriptors); + const {scales: newExposedScales, ...newScales} = createScaleFunctions(newScaleDescriptors); + Object.assign(scaleDescriptors, newScaleDescriptors); + Object.assign(scales, newScales); + Object.assign(scales.scales, newExposedScales); + } + + // Sort and filter the facets to match the fx and fy domains; this is needed + // because the facets were constructed prior to the fx and fy scales. + let facetDomains, facetTranslate; + if (facets !== undefined) { + facetDomains = {x: fx?.domain(), y: fy?.domain()}; + facets = recreateFacets(facets, facetDomains); + facetTranslate = facetTranslator(fx, fy, dimensions); + } + + // Compute value objects, applying scales and projection as needed. + for (const [mark, state] of stateByMark) { + state.values = mark.scale(state.channels, scales, context); + } + + const {width, height} = dimensions; + + d3.select(svg) + .attr("class", className) + .attr("fill", "currentColor") + .attr("font-family", "system-ui, sans-serif") + .attr("font-size", 10) + .attr("text-anchor", "middle") + .attr("width", width) + .attr("height", height) + .attr("viewBox", `0 0 ${width} ${height}`) + .attr("aria-label", ariaLabel) + .attr("aria-description", ariaDescription) + .call((svg) => + // Warning: if you edit this, change defaultClassName. + svg.append("style").text( + `.${className} { + --plot-background: white; + display: block; + height: auto; + height: intrinsic; + max-width: 100%; +} +.${className} text, +.${className} tspan { + white-space: pre; +}` + ) + ) + .call(applyInlineStyles, style); + + // Render marks. + for (const mark of marks) { + const {channels, values, facets: indexes} = stateByMark.get(mark); + + // Render a non-faceted mark. + if (facets === undefined || mark.facet === "super") { + let index = null; + if (indexes) { + index = indexes[0]; + index = mark.filter(index, channels, values); + if (index.length === 0) continue; + } + const node = mark.render(index, scales, values, superdimensions, context); + if (node == null) continue; + svg.appendChild(node); + } + + // Render a faceted mark. + else { + let g; + for (const f of facets) { + if (!(mark.facetAnchor?.(facets, facetDomains, f) ?? !f.empty)) continue; + let index = null; + if (indexes) { + const faceted = facetStateByMark.has(mark); + index = indexes[faceted ? f.i : 0]; + index = mark.filter(index, channels, values); + if (index.length === 0) continue; + if (!faceted && index === indexes[0]) index = subarray(index); // copy before assigning fx, fy, fi + (index.fx = f.x), (index.fy = f.y), (index.fi = f.i); + } + const node = mark.render(index, scales, values, subdimensions, context); + if (node == null) continue; + // Lazily construct the shared group (to drop empty marks). + (g ??= d3.select(svg).append("g")).append(() => node).datum(f); + // Promote ARIA attributes and mark transform to avoid repetition on + // each facet; this assumes that these attributes are consistent across + // facets, but that should be the case! + for (const name of ["aria-label", "aria-description", "aria-hidden", "transform"]) { + if (node.hasAttribute(name)) { + g.attr(name, node.getAttribute(name)); + node.removeAttribute(name); + } + } + } + g?.selectChildren().attr("transform", facetTranslate); + } + } + + // Wrap the plot in a figure, if needed. + const legends = createLegends(scaleDescriptors, context, options); + const {figure: figured = title != null || subtitle != null || caption != null || legends.length > 0} = options; + if (figured) { + figure = document.createElement("figure"); + figure.className = `${className}-figure`; + figure.style.maxWidth = "initial"; // avoid Observable default style + if (title != null) figure.append(createTitleElement(document, title, "h2")); + if (subtitle != null) figure.append(createTitleElement(document, subtitle, "h3")); + figure.append(...legends, svg); + if (caption != null) figure.append(createFigcaption(document, caption)); + } + + figure.scale = exposeScales(scales.scales); + figure.legend = exposeLegends(scaleDescriptors, context, options); + + const w = consumeWarnings(); + if (w > 0) { + d3.select(svg) + .append("text") + .attr("x", width) + .attr("y", 20) + .attr("dy", "-1em") + .attr("text-anchor", "end") + .attr("font-family", "initial") // fix emoji rendering in Chrome + .text("\u26a0\ufe0f") // emoji variation selector + .append("title") + .text(`${w.toLocaleString("en-US")} warning${w === 1 ? "" : "s"}. Please check the console.`); + } + + return figure; +} + +function createTitleElement(document, contents, tag) { + if (contents.ownerDocument) return contents; + const e = document.createElement(tag); + e.append(contents); + return e; +} + +function createFigcaption(document, caption) { + const e = document.createElement("figcaption"); + e.append(caption); + return e; +} + +function plotThis({marks = [], ...options} = {}) { + return plot({...options, marks: [...marks, this]}); +} + +// Note: This side-effect avoids a circular dependency. +Mark.prototype.plot = plotThis; + +function flatMarks(marks) { + return marks + .flat(Infinity) + .filter((mark) => mark != null) + .map(markify); +} + +function markify(mark) { + return typeof mark.render === "function" ? mark : new Render(mark); +} + +class Render extends Mark { + constructor(render) { + if (typeof render !== "function") throw new TypeError("invalid mark; missing render function"); + super(); + this.render = render; + } + render() {} +} + +// Note: mutates channel.value to apply the scale transform, if any. +function applyScaleTransforms(channels, options) { + for (const name in channels) applyScaleTransform(channels[name], options); + return channels; +} + +// Note: mutates channel.value to apply the scale transform, if any. Also sets +// channel.transform to false to prevent duplicate transform application. +function applyScaleTransform(channel, options) { + const {scale, transform: t = true} = channel; + if (scale == null || !t) return; + const { + type, + percent, + interval, + transform = percent ? (x) => x * 100 : maybeIntervalTransform(interval, type) + } = options[scale] ?? {}; + if (transform == null) return; + channel.value = map$1(channel.value, transform); + channel.transform = false; +} + +// An initializer may generate channels without knowing how the downstream mark +// will use them. Marks are typically responsible associated scales with +// channels, but here we assume common behavior across marks. +function inferChannelScales(channels) { + for (const name in channels) { + inferChannelScale(name, channels[name]); + } +} + +function addScaleChannels(channelsByScale, stateByMark, options, filter = yes) { + for (const {channels} of stateByMark.values()) { + for (const name in channels) { + const channel = channels[name]; + const {scale} = channel; + if (scale != null && filter(scale)) { + // Geo marks affect the default x and y domains if there is no + // projection. Skip this (as an optimization) when a projection is + // specified, or when the domains for x and y are specified. + if (scale === "projection") { + if (!hasProjection(options)) { + const gx = options.x?.domain === undefined; + const gy = options.y?.domain === undefined; + if (gx || gy) { + const [x, y] = getGeometryChannels(channel); + if (gx) addScaleChannel(channelsByScale, "x", x); + if (gy) addScaleChannel(channelsByScale, "y", y); + } + } + } else { + addScaleChannel(channelsByScale, scale, channel); + } + } + } + } + return channelsByScale; +} + +function addScaleChannel(channelsByScale, scale, channel) { + const scaleChannels = channelsByScale.get(scale); + if (scaleChannels !== undefined) scaleChannels.push(channel); + else channelsByScale.set(scale, [channel]); +} + +// Returns the facet groups, and possibly fx and fy channels, associated with +// the top-level facet option {data, x, y}. +function maybeTopFacet(facet, options) { + if (facet == null) return; + const {x, y} = facet; + if (x == null && y == null) return; + const data = arrayify(facet.data); + if (data == null) throw new Error("missing facet data"); + const channels = {}; + if (x != null) channels.fx = createChannel(data, {value: x, scale: "fx"}); + if (y != null) channels.fy = createChannel(data, {value: y, scale: "fy"}); + applyScaleTransforms(channels, options); + const groups = facetGroups(data, channels); + return {channels, groups, data: facet.data}; +} + +// Returns the facet groups, and possibly fx and fy channels, associated with a +// mark, either through top-level faceting or mark-level facet options {fx, fy}. +function maybeMarkFacet(mark, topFacetState, options) { + if (mark.facet === null || mark.facet === "super") return; + + // This mark defines a mark-level facet. TODO There’s some code duplication + // here with maybeTopFacet that we could reduce. + const {fx, fy} = mark; + if (fx != null || fy != null) { + const data = arrayify(mark.data ?? fx ?? fy); + if (data === undefined) throw new Error(`missing facet data in ${mark.ariaLabel}`); + if (data === null) return; // ignore channel definitions if no data is provided TODO this right? + const channels = {}; + if (fx != null) channels.fx = createChannel(data, {value: fx, scale: "fx"}); + if (fy != null) channels.fy = createChannel(data, {value: fy, scale: "fy"}); + applyScaleTransforms(channels, options); + return {channels, groups: facetGroups(data, channels)}; + } + + // This mark links to a top-level facet, if present. + if (topFacetState === undefined) return; + + // TODO Can we link the top-level facet channels here? + const {channels, groups, data} = topFacetState; + if (mark.facet !== "auto" || mark.data === data) return {channels, groups}; + + // Warn for the common pitfall of wanting to facet mapped data with the + // top-level facet option. + if ( + data.length > 0 && + (groups.size > 1 || (groups.size === 1 && channels.fx && channels.fy && [...groups][0][1].size > 1)) && + arrayify(mark.data)?.length === data.length + ) { + warn( + `Warning: the ${mark.ariaLabel} mark appears to use faceted data, but isn’t faceted. The mark data has the same length as the facet data and the mark facet option is "auto", but the mark data and facet data are distinct. If this mark should be faceted, set the mark facet option to true; otherwise, suppress this warning by setting the mark facet option to false.` + ); + } +} + +function derive(mark, options = {}) { + return initializer({...options, x: null, y: null}, (data, facets, channels, scales, dimensions, context) => { + return context.getMarkState(mark); + }); +} + +function inferTips(marks) { + const tips = []; + for (const mark of marks) { + let tipOptions = mark.tip; + if (tipOptions) { + if (tipOptions === true) tipOptions = {}; + else if (typeof tipOptions === "string") tipOptions = {pointer: tipOptions}; + let {pointer: p, preferredAnchor: a} = tipOptions; + p = /^x$/i.test(p) ? pointerX : /^y$/i.test(p) ? pointerY : pointer; // TODO validate? + tipOptions = p(derive(mark, tipOptions)); + tipOptions.title = null; // prevent implicit title for primitive data + if (a === undefined) tipOptions.preferredAnchor = p === pointerY ? "left" : "bottom"; + const t = tip(mark.data, tipOptions); + t.facet = mark.facet; // inherit facet settings + t.facetAnchor = mark.facetAnchor; // inherit facet settings + tips.push(t); + } + } + return tips; +} + +function inferAxes(marks, channelsByScale, options) { + let { + projection, + x = {}, + y = {}, + fx = {}, + fy = {}, + axis, + grid, + facet = {}, + facet: {axis: facetAxis = axis, grid: facetGrid} = facet, + x: {axis: xAxis = axis, grid: xGrid = xAxis === null ? null : grid} = x, + y: {axis: yAxis = axis, grid: yGrid = yAxis === null ? null : grid} = y, + fx: {axis: fxAxis = facetAxis, grid: fxGrid = fxAxis === null ? null : facetGrid} = fx, + fy: {axis: fyAxis = facetAxis, grid: fyGrid = fyAxis === null ? null : facetGrid} = fy + } = options; + + // Disable axes if the corresponding scale is not present. + if (projection || (!isScaleOptions(x) && !hasPositionChannel("x", marks))) xAxis = xGrid = null; + if (projection || (!isScaleOptions(y) && !hasPositionChannel("y", marks))) yAxis = yGrid = null; + if (!channelsByScale.has("fx")) fxAxis = fxGrid = null; + if (!channelsByScale.has("fy")) fyAxis = fyGrid = null; + + // Resolve the default implicit axes by checking for explicit ones. + if (xAxis === undefined) xAxis = !hasAxis(marks, "x"); + if (yAxis === undefined) yAxis = !hasAxis(marks, "y"); + if (fxAxis === undefined) fxAxis = !hasAxis(marks, "fx"); + if (fyAxis === undefined) fyAxis = !hasAxis(marks, "fy"); + + // Resolve the default orientation of axes. + if (xAxis === true) xAxis = "bottom"; + if (yAxis === true) yAxis = "left"; + if (fxAxis === true) fxAxis = xAxis === "top" || xAxis === null ? "bottom" : "top"; + if (fyAxis === true) fyAxis = yAxis === "right" || yAxis === null ? "left" : "right"; + + const axes = []; + maybeGrid(axes, fyGrid, gridFy, fy); + maybeAxis(axes, fyAxis, axisFy, "right", "left", facet, fy); + maybeGrid(axes, fxGrid, gridFx, fx); + maybeAxis(axes, fxAxis, axisFx, "top", "bottom", facet, fx); + maybeGrid(axes, yGrid, gridY, y); + maybeAxis(axes, yAxis, axisY, "left", "right", options, y); + maybeGrid(axes, xGrid, gridX, x); + maybeAxis(axes, xAxis, axisX, "bottom", "top", options, x); + return axes; +} + +function maybeAxis(axes, axis, axisType, primary, secondary, defaults, options) { + if (!axis) return; + const both = isBoth(axis); + options = axisOptions(both ? primary : axis, defaults, options); + const {line} = options; + if ((axisType === axisY || axisType === axisX) && line && !isNone(line)) axes.push(frame(lineOptions(options))); + axes.push(axisType(options)); + if (both) axes.push(axisType({...options, anchor: secondary, label: null})); +} + +function maybeGrid(axes, grid, gridType, options) { + if (!grid || isNone(grid)) return; + axes.push(gridType(gridOptions(grid, options))); +} + +function isBoth(value) { + return /^\s*both\s*$/i.test(value); +} + +function axisOptions( + anchor, + defaults, + { + line = defaults.line, + ticks, + tickSize, + tickSpacing, + tickPadding, + tickFormat, + tickRotate, + fontVariant, + ariaLabel, + ariaDescription, + label = defaults.label, + labelAnchor, + labelArrow = defaults.labelArrow, + labelOffset + } +) { + return { + anchor, + line, + ticks, + tickSize, + tickSpacing, + tickPadding, + tickFormat, + tickRotate, + fontVariant, + ariaLabel, + ariaDescription, + label, + labelAnchor, + labelArrow, + labelOffset + }; +} + +function lineOptions(options) { + const {anchor, line} = options; + return {anchor, facetAnchor: anchor + "-empty", stroke: line === true ? undefined : line}; +} + +function gridOptions( + grid, + { + stroke = isColor(grid) ? grid : undefined, + ticks = isGridTicks(grid) ? grid : undefined, + tickSpacing, + ariaLabel, + ariaDescription + } +) { + return { + stroke, + ticks, + tickSpacing, + ariaLabel, + ariaDescription + }; +} + +function isGridTicks(grid) { + switch (typeof grid) { + case "number": + return true; + case "string": + return !isColor(grid); + } + return isIterable(grid) || typeof grid?.range === "function"; +} + +// Is there an explicit axis already present? TODO We probably want a more +// explicit test than looking for the ARIA label, but it does afford some +// flexibility in axis implementation which is nice. +function hasAxis(marks, k) { + const prefix = `${k}-axis `; + return marks.some((m) => m.ariaLabel?.startsWith(prefix)); +} + +function hasPositionChannel(k, marks) { + for (const mark of marks) { + for (const key in mark.channels) { + const {scale} = mark.channels[key]; + if (scale === k || scale === "projection") { + return true; + } + } + } + return false; +} + +function inheritScaleLabels(newScales, scales) { + for (const key in newScales) { + const newScale = newScales[key]; + const scale = scales[key]; + if (newScale.label === undefined && scale) { + newScale.label = scale.label; + } + } + return newScales; +} + +// This differs from the other outerDimensions in that it accounts for rounding +// and outer padding in the facet scales; we want the frame to align exactly +// with the actual range, not the desired range. +function actualDimensions({fx, fy}, dimensions) { + const {marginTop, marginRight, marginBottom, marginLeft, width, height} = outerDimensions(dimensions); + const fxr = fx && outerRange(fx); + const fyr = fy && outerRange(fy); + return { + marginTop: fy ? fyr[0] : marginTop, + marginRight: fx ? width - fxr[1] : marginRight, + marginBottom: fy ? height - fyr[1] : marginBottom, + marginLeft: fx ? fxr[0] : marginLeft, + // Some marks, namely the x- and y-axis labels, want to know what the + // desired (rather than actual) margins are for positioning. + inset: { + marginTop: dimensions.marginTop, + marginRight: dimensions.marginRight, + marginBottom: dimensions.marginBottom, + marginLeft: dimensions.marginLeft + }, + width, + height + }; +} + +function outerRange(scale) { + const domain = scale.domain(); + let x1 = scale(domain[0]); + let x2 = scale(domain[domain.length - 1]); + if (x2 < x1) [x1, x2] = [x2, x1]; + return [x1, x2 + scale.bandwidth()]; +} + +const curves = new Map([ + ["basis", d3.curveBasis], + ["basis-closed", d3.curveBasisClosed], + ["basis-open", d3.curveBasisOpen], + ["bundle", d3.curveBundle], + ["bump-x", d3.curveBumpX], + ["bump-y", d3.curveBumpY], + ["cardinal", d3.curveCardinal], + ["cardinal-closed", d3.curveCardinalClosed], + ["cardinal-open", d3.curveCardinalOpen], + ["catmull-rom", d3.curveCatmullRom], + ["catmull-rom-closed", d3.curveCatmullRomClosed], + ["catmull-rom-open", d3.curveCatmullRomOpen], + ["linear", d3.curveLinear], + ["linear-closed", d3.curveLinearClosed], + ["monotone-x", d3.curveMonotoneX], + ["monotone-y", d3.curveMonotoneY], + ["natural", d3.curveNatural], + ["step", d3.curveStep], + ["step-after", d3.curveStepAfter], + ["step-before", d3.curveStepBefore] +]); + +function maybeCurve(curve = d3.curveLinear, tension) { + if (typeof curve === "function") return curve; // custom curve + const c = curves.get(`${curve}`.toLowerCase()); + if (!c) throw new Error(`unknown curve: ${curve}`); + if (tension !== undefined) { + if ("beta" in c) { + return c.beta(tension); + } else if ("tension" in c) { + return c.tension(tension); + } else if ("alpha" in c) { + return c.alpha(tension); + } + } + return c; +} + +// For the “auto” curve, return a symbol instead of a curve implementation; +// we’ll use d3.geoPath to render if there’s a projection. +function maybeCurveAuto(curve = curveAuto, tension) { + return typeof curve !== "function" && `${curve}`.toLowerCase() === "auto" ? curveAuto : maybeCurve(curve, tension); +} + +// This is a special built-in curve that will use d3.geoPath when there is a +// projection, and the linear curve when there is not. You can explicitly +// opt-out of d3.geoPath and instead use d3.line with the "linear" curve. +function curveAuto(context) { + return d3.curveLinear(context); +} + +// Group on {z, fill, stroke}, then optionally on y, then bin x. +function binX(outputs = {y: "count"}, options = {}) { + [outputs, options] = mergeOptions$2(outputs, options); + const {x, y} = options; + return binn(maybeBinValue(x, options, identity$1), null, null, y, outputs, maybeInsetX(options)); +} + +// Group on {z, fill, stroke}, then optionally on x, then bin y. +function binY(outputs = {x: "count"}, options = {}) { + [outputs, options] = mergeOptions$2(outputs, options); + const {x, y} = options; + return binn(null, maybeBinValue(y, options, identity$1), x, null, outputs, maybeInsetY(options)); +} + +// Group on {z, fill, stroke}, then bin on x and y. +function bin(outputs = {fill: "count"}, options = {}) { + [outputs, options] = mergeOptions$2(outputs, options); + const {x, y} = maybeBinValueTuple(options); + return binn(x, y, null, null, outputs, maybeInsetX(maybeInsetY(options))); +} + +function maybeDenseInterval(bin, k, options = {}) { + if (options?.interval == null) return options; + const {reduce = reduceFirst$1} = options; + const outputs = {filter: null}; + if (options[k] != null) outputs[k] = reduce; + if (options[`${k}1`] != null) outputs[`${k}1`] = reduce; + if (options[`${k}2`] != null) outputs[`${k}2`] = reduce; + return bin(outputs, options); +} + +function maybeDenseIntervalX(options = {}) { + return maybeDenseInterval(binX, "y", withTip(options, "x")); +} + +function maybeDenseIntervalY(options = {}) { + return maybeDenseInterval(binY, "x", withTip(options, "y")); +} + +function binn( + bx, // optionally bin on x (exclusive with gx) + by, // optionally bin on y (exclusive with gy) + gx, // optionally group on x (exclusive with bx and gy) + gy, // optionally group on y (exclusive with by and gx) + { + data: reduceData = reduceIdentity, // TODO avoid materializing when unused? + filter = reduceCount, // return only non-empty bins by default + sort, + reverse, + ...outputs // output channel definitions + } = {}, + inputs = {} // input channels and options +) { + bx = maybeBin(bx); + by = maybeBin(by); + + // Compute the outputs. + outputs = maybeBinOutputs(outputs, inputs); + reduceData = maybeBinReduce(reduceData, identity$1); + sort = sort == null ? undefined : maybeBinOutput("sort", sort, inputs); + filter = filter == null ? undefined : maybeBinEvaluator("filter", filter, inputs); + + // Don’t group on a channel if an output requires it as an input! + if (gx != null && hasOutput(outputs, "x", "x1", "x2")) gx = null; + if (gy != null && hasOutput(outputs, "y", "y1", "y2")) gy = null; + + // Produce x1, x2, y1, and y2 output channels as appropriate (when binning). + const [BX1, setBX1] = maybeColumn(bx); + const [BX2, setBX2] = maybeColumn(bx); + const [BY1, setBY1] = maybeColumn(by); + const [BY2, setBY2] = maybeColumn(by); + + // Produce x or y output channels as appropriate (when grouping). + const [k, gk] = gx != null ? [gx, "x"] : gy != null ? [gy, "y"] : []; + const [GK, setGK] = maybeColumn(k); + + // Greedily materialize the z, fill, and stroke channels (if channels and not + // constants) so that we can reference them for subdividing groups without + // computing them more than once. We also want to consume options that should + // only apply to this transform rather than passing them through to the next. + const { + x, + y, + z, + fill, + stroke, + x1, + x2, // consumed if x is an output + y1, + y2, // consumed if y is an output + domain, + cumulative, + thresholds, + interval, + ...options + } = inputs; + const [GZ, setGZ] = maybeColumn(z); + const [vfill] = maybeColorChannel(fill); + const [vstroke] = maybeColorChannel(stroke); + const [GF, setGF] = maybeColumn(vfill); + const [GS, setGS] = maybeColumn(vstroke); + + return { + ...("z" in inputs && {z: GZ || z}), + ...("fill" in inputs && {fill: GF || fill}), + ...("stroke" in inputs && {stroke: GS || stroke}), + ...basic(options, (data, facets, plotOptions) => { + const K = maybeApplyInterval(valueof(data, k), plotOptions?.[gk]); + const Z = valueof(data, z); + const F = valueof(data, vfill); + const S = valueof(data, vstroke); + const G = maybeSubgroup(outputs, {z: Z, fill: F, stroke: S}); + const groupFacets = []; + const groupData = []; + const GK = K && setGK([]); + const GZ = Z && setGZ([]); + const GF = F && setGF([]); + const GS = S && setGS([]); + const BX1 = bx && setBX1([]); + const BX2 = bx && setBX2([]); + const BY1 = by && setBY1([]); + const BY2 = by && setBY2([]); + const bin = bing(bx, by, data); + let i = 0; + for (const o of outputs) o.initialize(data); + if (sort) sort.initialize(data); + if (filter) filter.initialize(data); + for (const facet of facets) { + const groupFacet = []; + for (const o of outputs) o.scope("facet", facet); + if (sort) sort.scope("facet", facet); + if (filter) filter.scope("facet", facet); + for (const [f, I] of maybeGroup(facet, G)) { + for (const [k, g] of maybeGroup(I, K)) { + for (const [b, extent] of bin(g)) { + if (filter && !filter.reduce(b, extent)) continue; + groupFacet.push(i++); + groupData.push(reduceData.reduceIndex(b, data, extent)); + if (K) GK.push(k); + if (Z) GZ.push(G === Z ? f : Z[(b.length > 0 ? b : g)[0]]); + if (F) GF.push(G === F ? f : F[(b.length > 0 ? b : g)[0]]); + if (S) GS.push(G === S ? f : S[(b.length > 0 ? b : g)[0]]); + if (BX1) BX1.push(extent.x1), BX2.push(extent.x2); + if (BY1) BY1.push(extent.y1), BY2.push(extent.y2); + for (const o of outputs) o.reduce(b, extent); + if (sort) sort.reduce(b); + } + } + } + groupFacets.push(groupFacet); + } + maybeSort(groupFacets, sort, reverse); + return {data: groupData, facets: groupFacets}; + }), + ...(!hasOutput(outputs, "x") && (BX1 ? {x1: BX1, x2: BX2, x: mid(BX1, BX2)} : {x, x1, x2})), + ...(!hasOutput(outputs, "y") && (BY1 ? {y1: BY1, y2: BY2, y: mid(BY1, BY2)} : {y, y1, y2})), + ...(GK && {[gk]: GK}), + ...Object.fromEntries(outputs.map(({name, output}) => [name, output])) + }; +} + +// Allow bin options to be specified as part of outputs; merge them into options. +function mergeOptions$2({cumulative, domain, thresholds, interval, ...outputs}, options) { + return [outputs, {cumulative, domain, thresholds, interval, ...options}]; +} + +function maybeBinValue(value, {cumulative, domain, thresholds, interval}, defaultValue) { + value = {...maybeValue(value)}; + if (value.domain === undefined) value.domain = domain; + if (value.cumulative === undefined) value.cumulative = cumulative; + if (value.thresholds === undefined) value.thresholds = thresholds; + if (value.interval === undefined) value.interval = interval; + if (value.value === undefined) value.value = defaultValue; + value.thresholds = maybeThresholds(value.thresholds, value.interval); + return value; +} + +function maybeBinValueTuple(options) { + let {x, y} = options; + x = maybeBinValue(x, options); + y = maybeBinValue(y, options); + [x.value, y.value] = maybeTuple(x.value, y.value); + return {x, y}; +} + +function maybeBin(options) { + if (options == null) return; + const {value, cumulative, domain = d3.extent, thresholds} = options; + const bin = (data) => { + let V = valueof(data, value); + let T; // bin thresholds + if (isTemporal(V) || isTimeThresholds(thresholds)) { + V = map$1(V, coerceDate, Float64Array); // like coerceDates, but faster + let [min, max] = typeof domain === "function" ? domain(V) : domain; + let t = typeof thresholds === "function" && !isInterval(thresholds) ? thresholds(V, min, max) : thresholds; + if (typeof t === "number") t = d3.utcTickInterval(min, max, t); + if (isInterval(t)) { + if (domain === d3.extent) { + min = t.floor(min); + max = t.offset(t.floor(max)); + } + t = t.range(min, t.offset(max)); + } + T = t; + } else { + V = coerceNumbers(V); + let [min, max] = typeof domain === "function" ? domain(V) : domain; + let t = typeof thresholds === "function" && !isInterval(thresholds) ? thresholds(V, min, max) : thresholds; + if (typeof t === "number") { + // This differs from d3.ticks with regard to exclusive bounds: we want a + // first threshold less than or equal to the minimum, and a last + // threshold (strictly) greater than the maximum. + if (domain === d3.extent) { + let step = d3.tickIncrement(min, max, t); + if (isFinite(step)) { + if (step > 0) { + let r0 = Math.round(min / step); + let r1 = Math.round(max / step); + if (!(r0 * step <= min)) --r0; + if (!(r1 * step > max)) ++r1; + let n = r1 - r0 + 1; + t = new Float64Array(n); + for (let i = 0; i < n; ++i) t[i] = (r0 + i) * step; + } else if (step < 0) { + step = -step; + let r0 = Math.round(min * step); + let r1 = Math.round(max * step); + if (!(r0 / step <= min)) --r0; + if (!(r1 / step > max)) ++r1; + let n = r1 - r0 + 1; + t = new Float64Array(n); + for (let i = 0; i < n; ++i) t[i] = (r0 + i) / step; + } else { + t = [min]; + } + } else { + t = [min]; + } + } else { + t = d3.ticks(min, max, t); + } + } else if (isInterval(t)) { + if (domain === d3.extent) { + min = t.floor(min); + max = t.offset(t.floor(max)); + } + t = t.range(min, t.offset(max)); + } + T = t; + } + const E = []; + if (T.length === 1) E.push([T[0], T[0]]); // collapsed domain + else for (let i = 1; i < T.length; ++i) E.push([T[i - 1], T[i]]); + E.bin = (cumulative < 0 ? bin1cn : cumulative > 0 ? bin1cp : bin1)(E, T, V); + return E; + }; + bin.label = labelof(value); + return bin; +} + +function maybeThresholds(thresholds, interval, defaultThresholds = thresholdAuto) { + if (thresholds === undefined) { + return interval === undefined ? defaultThresholds : maybeRangeInterval(interval); + } + if (typeof thresholds === "string") { + switch (thresholds.toLowerCase()) { + case "freedman-diaconis": + return d3.thresholdFreedmanDiaconis; + case "scott": + return d3.thresholdScott; + case "sturges": + return d3.thresholdSturges; + case "auto": + return thresholdAuto; + } + return maybeUtcInterval(thresholds); + } + return thresholds; // pass array, count, or function to bin.thresholds +} + +function maybeBinOutputs(outputs, inputs) { + return maybeOutputs(outputs, inputs, maybeBinOutput); +} + +function maybeBinOutput(name, reduce, inputs) { + return maybeOutput(name, reduce, inputs, maybeBinEvaluator); +} + +function maybeBinEvaluator(name, reduce, inputs) { + return maybeEvaluator(name, reduce, inputs, maybeBinReduce); +} + +function maybeBinReduce(reduce, value) { + return maybeReduce$1(reduce, value, maybeBinReduceFallback); +} + +function maybeBinReduceFallback(reduce) { + switch (`${reduce}`.toLowerCase()) { + case "x": + return reduceX; + case "x1": + return reduceX1; + case "x2": + return reduceX2; + case "y": + return reduceY; + case "y1": + return reduceY1; + case "y2": + return reduceY2; + } + throw new Error(`invalid bin reduce: ${reduce}`); +} + +function thresholdAuto(values, min, max) { + return Math.min(200, d3.thresholdScott(values, min, max)); +} + +function isTimeThresholds(t) { + return isTimeInterval(t) || (isIterable(t) && isTemporal(t)); +} + +function bing(bx, by, data) { + const EX = bx?.(data); + const EY = by?.(data); + return EX && EY + ? function* (I) { + const X = EX.bin(I); // first bin on x + for (const [ix, [x1, x2]] of EX.entries()) { + const Y = EY.bin(X[ix]); // then bin on y + for (const [iy, [y1, y2]] of EY.entries()) { + yield [Y[iy], {data, x1, y1, x2, y2}]; + } + } + } + : EX + ? function* (I) { + const X = EX.bin(I); + for (const [i, [x1, x2]] of EX.entries()) { + yield [X[i], {data, x1, x2}]; + } + } + : function* (I) { + const Y = EY.bin(I); + for (const [i, [y1, y2]] of EY.entries()) { + yield [Y[i], {data, y1, y2}]; + } + }; +} + +// non-cumulative distribution +function bin1(E, T, V) { + T = coerceNumbers(T); // for faster bisection + return (I) => { + const B = E.map(() => []); + for (const i of I) B[d3.bisect(T, V[i]) - 1]?.push(i); // TODO quantization? + return B; + }; +} + +// cumulative distribution +function bin1cp(E, T, V) { + const bin = bin1(E, T, V); + return (I) => { + const B = bin(I); + for (let i = 1, n = B.length; i < n; ++i) { + const C = B[i - 1]; + const b = B[i]; + for (const j of C) b.push(j); + } + return B; + }; +} + +// complementary cumulative distribution +function bin1cn(E, T, V) { + const bin = bin1(E, T, V); + return (I) => { + const B = bin(I); + for (let i = B.length - 2; i >= 0; --i) { + const C = B[i + 1]; + const b = B[i]; + for (const j of C) b.push(j); + } + return B; + }; +} + +function mid1(x1, x2) { + const m = (+x1 + +x2) / 2; + return x1 instanceof Date ? new Date(m) : m; +} + +const reduceX = { + reduceIndex(I, X, {x1, x2}) { + return mid1(x1, x2); + } +}; + +const reduceY = { + reduceIndex(I, X, {y1, y2}) { + return mid1(y1, y2); + } +}; + +const reduceX1 = { + reduceIndex(I, X, {x1}) { + return x1; + } +}; + +const reduceX2 = { + reduceIndex(I, X, {x2}) { + return x2; + } +}; + +const reduceY1 = { + reduceIndex(I, X, {y1}) { + return y1; + } +}; + +const reduceY2 = { + reduceIndex(I, X, {y2}) { + return y2; + } +}; + +function maybeIdentityX(options = {}) { + return hasX(options) ? options : {...options, x: identity$1}; +} + +function maybeIdentityY(options = {}) { + return hasY(options) ? options : {...options, y: identity$1}; +} + +function exclusiveFacets(data, facets) { + if (facets.length === 1) return {data, facets}; // only one facet; trivially exclusive + + const n = data.length; + const O = new Uint8Array(n); + let overlaps = 0; + + // Count the number of overlapping indexes across facets. + for (const facet of facets) { + for (const i of facet) { + if (O[i]) ++overlaps; + O[i] = 1; + } + } + + // Do nothing if the facets are already exclusive. + if (overlaps === 0) return {data, facets}; // facets are exclusive + + // For each overlapping index (duplicate), assign a new unique index at the + // end of the existing array, duplicating the datum. For example, [[0, 1, 2], + // [2, 1, 3]] would become [[0, 1, 2], [4, 5, 3]]. Also attach a reindex to + // the data to preserve the association of channel values specified as arrays. + data = slice(data); + const R = (data[reindex] = new Uint32Array(n + overlaps)); + facets = facets.map((facet) => slice(facet, Uint32Array)); + let j = n; + O.fill(0); + for (const facet of facets) { + for (let k = 0, m = facet.length; k < m; ++k) { + const i = facet[k]; + if (O[i]) (facet[k] = j), (data[j] = data[i]), (R[j] = i), ++j; + else R[i] = i; + O[i] = 1; + } + } + + return {data, facets}; +} + +function stackX(stackOptions = {}, options = {}) { + if (arguments.length === 1) [stackOptions, options] = mergeOptions$1(stackOptions); + const {y1, y = y1, x, ...rest} = options; // note: consumes x! + const [transform, Y, x1, x2] = stack(y, x, "y", "x", stackOptions, rest); + return {...transform, y1, y: Y, x1, x2, x: mid(x1, x2)}; +} + +function stackX1(stackOptions = {}, options = {}) { + if (arguments.length === 1) [stackOptions, options] = mergeOptions$1(stackOptions); + const {y1, y = y1, x} = options; + const [transform, Y, X] = stack(y, x, "y", "x", stackOptions, options); + return {...transform, y1, y: Y, x: X}; +} + +function stackX2(stackOptions = {}, options = {}) { + if (arguments.length === 1) [stackOptions, options] = mergeOptions$1(stackOptions); + const {y1, y = y1, x} = options; + const [transform, Y, , X] = stack(y, x, "y", "x", stackOptions, options); + return {...transform, y1, y: Y, x: X}; +} + +function stackY(stackOptions = {}, options = {}) { + if (arguments.length === 1) [stackOptions, options] = mergeOptions$1(stackOptions); + const {x1, x = x1, y, ...rest} = options; // note: consumes y! + const [transform, X, y1, y2] = stack(x, y, "x", "y", stackOptions, rest); + return {...transform, x1, x: X, y1, y2, y: mid(y1, y2)}; +} + +function stackY1(stackOptions = {}, options = {}) { + if (arguments.length === 1) [stackOptions, options] = mergeOptions$1(stackOptions); + const {x1, x = x1, y} = options; + const [transform, X, Y] = stack(x, y, "x", "y", stackOptions, options); + return {...transform, x1, x: X, y: Y}; +} + +function stackY2(stackOptions = {}, options = {}) { + if (arguments.length === 1) [stackOptions, options] = mergeOptions$1(stackOptions); + const {x1, x = x1, y} = options; + const [transform, X, , Y] = stack(x, y, "x", "y", stackOptions, options); + return {...transform, x1, x: X, y: Y}; +} + +function maybeStackX({x, x1, x2, ...options} = {}) { + options = withTip(options, "y"); + if (x1 === undefined && x2 === undefined) return stackX({x, ...options}); + [x1, x2] = maybeZero(x, x1, x2); + return {...options, x1, x2}; +} + +function maybeStackY({y, y1, y2, ...options} = {}) { + options = withTip(options, "x"); + if (y1 === undefined && y2 === undefined) return stackY({y, ...options}); + [y1, y2] = maybeZero(y, y1, y2); + return {...options, y1, y2}; +} + +// The reverse option is ambiguous: it is both a stack option and a basic +// transform. If only one options object is specified, we interpret it as a +// stack option, and therefore must remove it from the propagated options. +function mergeOptions$1(options) { + const {offset, order, reverse, ...rest} = options; + return [{offset, order, reverse}, rest]; +} + +// This is a hint to the tooltip mark that the y1 and y2 channels (for stackY, +// or conversely x1 and x2 for stackX) represent a stacked length, and that the +// tooltip should therefore show y2-y1 instead of an extent. +const lengthy = {length: true}; + +function stack(x, y = one, kx, ky, {offset, order, reverse}, options) { + if (y === null) throw new Error(`stack requires ${ky}`); + const z = maybeZ(options); + const [X, setX] = maybeColumn(x); + const [Y1, setY1] = column(y); + const [Y2, setY2] = column(y); + Y1.hint = Y2.hint = lengthy; + offset = maybeOffset(offset); + order = maybeOrder(order, offset, ky); + return [ + basic(options, (data, facets, plotOptions) => { + ({data, facets} = exclusiveFacets(data, facets)); + const X = x == null ? undefined : setX(maybeApplyInterval(valueof(data, x), plotOptions?.[kx])); + const Y = valueof(data, y, Float64Array); + const Z = valueof(data, z); + const compare = order && order(data, X, Y, Z); + const n = data.length; + const Y1 = setY1(new Float64Array(n)); + const Y2 = setY2(new Float64Array(n)); + const facetstacks = []; + for (const facet of facets) { + const stacks = X ? Array.from(d3.group(facet, (i) => X[i]).values()) : [facet]; + if (compare) for (const stack of stacks) stack.sort(compare); + for (const stack of stacks) { + let yn = 0; + let yp = 0; + if (reverse) stack.reverse(); + for (const i of stack) { + const y = Y[i]; + if (y < 0) yn = Y2[i] = (Y1[i] = yn) + y; + else if (y > 0) yp = Y2[i] = (Y1[i] = yp) + y; + else Y2[i] = Y1[i] = yp; // NaN or zero + } + } + facetstacks.push(stacks); + } + if (offset) offset(facetstacks, Y1, Y2, Z); + return {data, facets}; + }), + X, + Y1, + Y2 + ]; +} + +function maybeOffset(offset) { + if (offset == null) return; + if (typeof offset === "function") return offset; + switch (`${offset}`.toLowerCase()) { + case "expand": + case "normalize": + return offsetExpand; + case "center": + case "silhouette": + return offsetCenter; + case "wiggle": + return offsetWiggle; + } + throw new Error(`unknown offset: ${offset}`); +} + +// Given a single stack, returns the minimum and maximum values from the given +// Y2 column. Note that this relies on Y2 always being the outer column for +// diverging values. +function extent(stack, Y2) { + let min = 0, + max = 0; + for (const i of stack) { + const y = Y2[i]; + if (y < min) min = y; + if (y > max) max = y; + } + return [min, max]; +} + +function offsetExpand(facetstacks, Y1, Y2) { + for (const stacks of facetstacks) { + for (const stack of stacks) { + const [yn, yp] = extent(stack, Y2); + for (const i of stack) { + const m = 1 / (yp - yn || 1); + Y1[i] = m * (Y1[i] - yn); + Y2[i] = m * (Y2[i] - yn); + } + } + } +} + +function offsetCenter(facetstacks, Y1, Y2) { + for (const stacks of facetstacks) { + for (const stack of stacks) { + const [yn, yp] = extent(stack, Y2); + for (const i of stack) { + const m = (yp + yn) / 2; + Y1[i] -= m; + Y2[i] -= m; + } + } + offsetZero(stacks, Y1, Y2); + } + offsetCenterFacets(facetstacks, Y1, Y2); +} + +function offsetWiggle(facetstacks, Y1, Y2, Z) { + for (const stacks of facetstacks) { + const prev = new d3.InternMap(); + let y = 0; + for (const stack of stacks) { + let j = -1; + const Fi = stack.map((i) => Math.abs(Y2[i] - Y1[i])); + const Df = stack.map((i) => { + j = Z ? Z[i] : ++j; + const value = Y2[i] - Y1[i]; + const diff = prev.has(j) ? value - prev.get(j) : 0; + prev.set(j, value); + return diff; + }); + const Cf1 = [0, ...d3.cumsum(Df)]; + for (const i of stack) { + Y1[i] += y; + Y2[i] += y; + } + const s1 = d3.sum(Fi); + if (s1) y -= d3.sum(Fi, (d, i) => (Df[i] / 2 + Cf1[i]) * d) / s1; + } + offsetZero(stacks, Y1, Y2); + } + offsetCenterFacets(facetstacks, Y1, Y2); +} + +function offsetZero(stacks, Y1, Y2) { + const m = d3.min(stacks, (stack) => d3.min(stack, (i) => Y1[i])); + for (const stack of stacks) { + for (const i of stack) { + Y1[i] -= m; + Y2[i] -= m; + } + } +} + +function offsetCenterFacets(facetstacks, Y1, Y2) { + const n = facetstacks.length; + if (n === 1) return; + const facets = facetstacks.map((stacks) => stacks.flat()); + const m = facets.map((I) => (d3.min(I, (i) => Y1[i]) + d3.max(I, (i) => Y2[i])) / 2); + const m0 = d3.min(m); + for (let j = 0; j < n; j++) { + const p = m0 - m[j]; + for (const i of facets[j]) { + Y1[i] += p; + Y2[i] += p; + } + } +} + +function maybeOrder(order, offset, ky) { + if (order === undefined && offset === offsetWiggle) return orderInsideOut(ascendingDefined); + if (order == null) return; + if (typeof order === "string") { + const negate = order.startsWith("-"); + const compare = negate ? descendingDefined : ascendingDefined; + switch ((negate ? order.slice(1) : order).toLowerCase()) { + case "value": + case ky: + return orderY(compare); + case "z": + return orderZ(compare); + case "sum": + return orderSum(compare); + case "appearance": + return orderAppearance(compare); + case "inside-out": + return orderInsideOut(compare); + } + return orderAccessor(field(order)); + } + if (typeof order === "function") return (order.length === 1 ? orderAccessor : orderComparator)(order); + if (Array.isArray(order)) return orderGiven(order); + throw new Error(`invalid order: ${order}`); +} + +// by value +function orderY(compare) { + return (data, X, Y) => (i, j) => compare(Y[i], Y[j]); +} + +// by location +function orderZ(compare) { + return (data, X, Y, Z) => (i, j) => compare(Z[i], Z[j]); +} + +// by sum of value (a.k.a. “ascending”) +function orderSum(compare) { + return orderZDomain(compare, (data, X, Y, Z) => + d3.groupSort( + range(data), + (I) => d3.sum(I, (i) => Y[i]), + (i) => Z[i] + ) + ); +} + +// by x = argmax of value +function orderAppearance(compare) { + return orderZDomain(compare, (data, X, Y, Z) => + d3.groupSort( + range(data), + (I) => X[d3.greatest(I, (i) => Y[i])], + (i) => Z[i] + ) + ); +} + +// by x = argmax of value, but rearranged inside-out by alternating series +// according to the sign of a running divergence of sums +function orderInsideOut(compare) { + return orderZDomain(compare, (data, X, Y, Z) => { + const I = range(data); + const K = d3.groupSort( + I, + (I) => X[d3.greatest(I, (i) => Y[i])], + (i) => Z[i] + ); + const sums = d3.rollup( + I, + (I) => d3.sum(I, (i) => Y[i]), + (i) => Z[i] + ); + const Kp = [], + Kn = []; + let s = 0; + for (const k of K) { + if (s < 0) { + s += sums.get(k); + Kp.push(k); + } else { + s -= sums.get(k); + Kn.push(k); + } + } + return Kn.reverse().concat(Kp); + }); +} + +function orderAccessor(f) { + return (data) => { + const O = valueof(data, f); + return (i, j) => ascendingDefined(O[i], O[j]); + }; +} + +function orderComparator(f) { + return (data) => (i, j) => f(data[i], data[j]); +} + +function orderGiven(domain) { + return orderZDomain(ascendingDefined, () => domain); +} + +// Given an ordering (domain) of distinct values in z that can be derived from +// the data, returns a comparator that can be used to sort stacks. Note that +// this is a series order: it will be consistent across stacks. +function orderZDomain(compare, domain) { + return (data, X, Y, Z) => { + if (!Z) throw new Error("missing channel: z"); + const map = new d3.InternMap(domain(data, X, Y, Z).map((d, i) => [d, i])); + return (i, j) => compare(map.get(Z[i]), map.get(Z[j])); + }; +} + +const defaults$g = { + ariaLabel: "area", + strokeWidth: 1, + strokeLinecap: "round", + strokeLinejoin: "round", + strokeMiterlimit: 1 +}; + +class Area extends Mark { + constructor(data, options = {}) { + const {x1, y1, x2, y2, z, curve, tension} = options; + super( + data, + { + x1: {value: x1, scale: "x"}, + y1: {value: y1, scale: "y"}, + x2: {value: x2, scale: "x", optional: true}, + y2: {value: y2, scale: "y", optional: true}, + z: {value: maybeZ(options), optional: true} + }, + options, + defaults$g + ); + this.z = z; + this.curve = maybeCurve(curve, tension); + } + filter(index) { + return index; + } + render(index, scales, channels, dimensions, context) { + const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, scales, 0, 0) + .call((g) => + g + .selectAll() + .data(groupIndex(index, [X1, Y1, X2, Y2], this, channels)) + .enter() + .append("path") + .call(applyDirectStyles, this) + .call(applyGroupedChannelStyles, this, channels) + .attr( + "d", + d3.area() + .curve(this.curve) + .defined((i) => i >= 0) + .x0((i) => X1[i]) + .y0((i) => Y1[i]) + .x1((i) => X2[i]) + .y1((i) => Y2[i]) + ) + ) + .node(); + } +} + +function area(data, options) { + if (options === undefined) return areaY(data, {x: first, y: second}); + return new Area(data, options); +} + +function areaX(data, options) { + const {y = indexOf, ...rest} = maybeDenseIntervalY(options); + return new Area(data, maybeStackX(maybeIdentityX({...rest, y1: y, y2: undefined}))); +} + +function areaY(data, options) { + const {x = indexOf, ...rest} = maybeDenseIntervalX(options); + return new Area(data, maybeStackY(maybeIdentityY({...rest, x1: x, x2: undefined}))); +} + +const defaults$f = { + ariaLabel: "link", + fill: "none", + stroke: "currentColor", + strokeMiterlimit: 1 +}; + +class Link extends Mark { + constructor(data, options = {}) { + const {x1, y1, x2, y2, curve, tension} = options; + super( + data, + { + x1: {value: x1, scale: "x"}, + y1: {value: y1, scale: "y"}, + x2: {value: x2, scale: "x", optional: true}, + y2: {value: y2, scale: "y", optional: true} + }, + options, + defaults$f + ); + this.curve = maybeCurveAuto(curve, tension); + markers(this, options); + } + project(channels, values, context) { + // For the auto curve, projection is handled at render. + if (this.curve !== curveAuto) { + super.project(channels, values, context); + } + } + render(index, scales, channels, dimensions, context) { + const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1} = channels; + const {curve} = this; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, scales) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("path") + .call(applyDirectStyles, this) + .attr( + "d", + curve === curveAuto && context.projection + ? sphereLink(context.projection, X1, Y1, X2, Y2) + : (i) => { + const p = d3.pathRound(); + const c = curve(p); + c.lineStart(); + c.point(X1[i], Y1[i]); + c.point(X2[i], Y2[i]); + c.lineEnd(); + return p; + } + ) + .call(applyChannelStyles, this, channels) + .call(applyMarkers, this, channels, context) + ) + .node(); + } +} + +function sphereLink(projection, X1, Y1, X2, Y2) { + const path = d3.geoPath(projection); + X1 = coerceNumbers(X1); + Y1 = coerceNumbers(Y1); + X2 = coerceNumbers(X2); + Y2 = coerceNumbers(Y2); + return (i) => + path({ + type: "LineString", + coordinates: [ + [X1[i], Y1[i]], + [X2[i], Y2[i]] + ] + }); +} + +function link(data, {x, x1, x2, y, y1, y2, ...options} = {}) { + [x1, x2] = maybeSameValue(x, x1, x2); + [y1, y2] = maybeSameValue(y, y1, y2); + return new Link(data, {...options, x1, x2, y1, y2}); +} + +// If x1 and x2 are specified, return them as {x1, x2}. +// If x and x1 and specified, or x and x2 are specified, return them as {x1, x2}. +// If only x, x1, or x2 are specified, return it as {x1}. +function maybeSameValue(x, x1, x2) { + if (x === undefined) { + if (x1 === undefined) { + if (x2 !== undefined) return [x2]; + } else { + if (x2 === undefined) return [x1]; + } + } else if (x1 === undefined) { + return x2 === undefined ? [x] : [x, x2]; + } else if (x2 === undefined) { + return [x, x1]; + } + return [x1, x2]; +} + +const defaults$e = { + ariaLabel: "arrow", + fill: "none", + stroke: "currentColor", + strokeLinecap: "round", + strokeMiterlimit: 1, + strokeWidth: 1.5 +}; + +class Arrow extends Mark { + constructor(data, options = {}) { + const { + x1, + y1, + x2, + y2, + bend = 0, + headAngle = 60, + headLength = 8, // Disable the arrow with headLength = 0; or, use Plot.link. + inset = 0, + insetStart = inset, + insetEnd = inset, + sweep + } = options; + super( + data, + { + x1: {value: x1, scale: "x"}, + y1: {value: y1, scale: "y"}, + x2: {value: x2, scale: "x", optional: true}, + y2: {value: y2, scale: "y", optional: true} + }, + options, + defaults$e + ); + this.bend = bend === true ? 22.5 : Math.max(-90, Math.min(90, bend)); + this.headAngle = +headAngle; + this.headLength = +headLength; + this.insetStart = +insetStart; + this.insetEnd = +insetEnd; + this.sweep = maybeSweep(sweep); + } + render(index, scales, channels, dimensions, context) { + const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1, SW} = channels; + const {strokeWidth, bend, headAngle, headLength, insetStart, insetEnd} = this; + const sw = SW ? (i) => SW[i] : constant(strokeWidth === undefined ? 1 : strokeWidth); + + // The angle between the arrow’s shaft and one of the wings; the “head” + // angle between the wings is twice this value. + const wingAngle = (headAngle * radians) / 2; + + // The length of the arrowhead’s “wings” (the line segments that extend from + // the end point) relative to the stroke width. + const wingScale = headLength / 1.5; + + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, scales) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("path") + .call(applyDirectStyles, this) + .attr("d", (i) => { + // The start ⟨x1,y1⟩ and end ⟨x2,y2⟩ points may be inset, and the + // ending line angle may be altered for inset swoopy arrows. + let x1 = X1[i], + y1 = Y1[i], + x2 = X2[i], + y2 = Y2[i]; + const lineLength = Math.hypot(x2 - x1, y2 - y1); + if (lineLength <= insetStart + insetEnd) return null; + let lineAngle = Math.atan2(y2 - y1, x2 - x1); + + // We don’t allow the wing length to be too large relative to the + // length of the arrow. (Plot.vector allows arbitrarily large + // wings, but that’s okay since vectors are usually small.) + const headLength = Math.min(wingScale * sw(i), lineLength / 3); + + // When bending, the offset between the straight line between the two points + // and the outgoing tangent from the start point. (Also the negative + // incoming tangent to the end point.) This must be within ±π/2. A positive + // angle will produce a clockwise curve; a negative angle will produce a + // counterclockwise curve; zero will produce a straight line. + const bendAngle = this.sweep(x1, y1, x2, y2) * bend * radians; + + // The radius of the circle that intersects with the two endpoints + // and has the specified bend angle. + const r = Math.hypot(lineLength / Math.tan(bendAngle), lineLength) / 2; + + // Apply insets. + if (insetStart || insetEnd) { + if (r < 1e5) { + // For inset swoopy arrows, compute the circle-circle + // intersection between a circle centered around the + // respective arrow endpoint and the center of the circle + // segment that forms the shaft of the arrow. + const sign = Math.sign(bendAngle); + const [cx, cy] = pointPointCenter([x1, y1], [x2, y2], r, sign); + if (insetStart) { + [x1, y1] = circleCircleIntersect([cx, cy, r], [x1, y1, insetStart], -sign * Math.sign(insetStart)); + } + // For the end inset, rotate the arrowhead so that it aligns + // with the truncated end of the arrow. Since the arrow is a + // segment of the circle centered at ⟨cx,cy⟩, we can compute + // the angular difference to the new endpoint. + if (insetEnd) { + const [x, y] = circleCircleIntersect([cx, cy, r], [x2, y2, insetEnd], sign * Math.sign(insetEnd)); + lineAngle += Math.atan2(y - cy, x - cx) - Math.atan2(y2 - cy, x2 - cx); + (x2 = x), (y2 = y); + } + } else { + // For inset straight arrows, offset along the straight line. + const dx = x2 - x1, + dy = y2 - y1, + d = Math.hypot(dx, dy); + if (insetStart) (x1 += (dx / d) * insetStart), (y1 += (dy / d) * insetStart); + if (insetEnd) (x2 -= (dx / d) * insetEnd), (y2 -= (dy / d) * insetEnd); + } + } + + // The angle of the arrow as it approaches the endpoint, and the + // angles of the adjacent wings. Here “left” refers to if the + // arrow is pointing up. + const endAngle = lineAngle + bendAngle; + const leftAngle = endAngle + wingAngle; + const rightAngle = endAngle - wingAngle; + + // The endpoints of the two wings. + const x3 = x2 - headLength * Math.cos(leftAngle); + const y3 = y2 - headLength * Math.sin(leftAngle); + const x4 = x2 - headLength * Math.cos(rightAngle); + const y4 = y2 - headLength * Math.sin(rightAngle); + + // If the radius is very large (or even infinite, as when the bend + // angle is zero), then render a straight line. + const a = r < 1e5 ? `A${r},${r} 0,0,${bendAngle > 0 ? 1 : 0} ` : `L`; + const h = headLength ? `M${x3},${y3}L${x2},${y2}L${x4},${y4}` : ""; + return `M${x1},${y1}${a}${x2},${y2}${h}`; + }) + .call(applyChannelStyles, this, channels) + ) + .node(); + } +} + +// Maybe flip the bend angle, depending on the arrow orientation. +function maybeSweep(sweep = 1) { + if (typeof sweep === "number") return constant(Math.sign(sweep)); + if (typeof sweep === "function") return (x1, y1, x2, y2) => Math.sign(sweep(x1, y1, x2, y2)); + switch (keyword(sweep, "sweep", ["+x", "-x", "+y", "-y"])) { + case "+x": + return (x1, y1, x2) => d3.ascending(x1, x2); + case "-x": + return (x1, y1, x2) => d3.descending(x1, x2); + case "+y": + return (x1, y1, x2, y2) => d3.ascending(y1, y2); + case "-y": + return (x1, y1, x2, y2) => d3.descending(y1, y2); + } +} + +// Returns the center of a circle that goes through the two given points ⟨ax,ay⟩ +// and ⟨bx,by⟩ and has radius r. There are two such points; use the sign +1 or +// -1 to choose between them. Returns [NaN, NaN] if r is too small. +function pointPointCenter([ax, ay], [bx, by], r, sign) { + const dx = bx - ax, + dy = by - ay, + d = Math.hypot(dx, dy); + const k = (sign * Math.sqrt(r * r - (d * d) / 4)) / d; + return [(ax + bx) / 2 - dy * k, (ay + by) / 2 + dx * k]; +} + +// Given two circles, one centered at ⟨ax,ay⟩ with radius ar, and the other +// centered at ⟨bx,by⟩ with radius br, returns a point at which the two circles +// intersect. There are typically two such points; use the sign +1 or -1 to +// chose between them. Returns [NaN, NaN] if there is no intersection. +// https://mathworld.wolfram.com/Circle-CircleIntersection.html +function circleCircleIntersect([ax, ay, ar], [bx, by, br], sign) { + const dx = bx - ax, + dy = by - ay, + d = Math.hypot(dx, dy); + const x = (dx * dx + dy * dy - br * br + ar * ar) / (2 * d); + const y = sign * Math.sqrt(ar * ar - x * x); + return [ax + (dx * x + dy * y) / d, ay + (dy * x - dx * y) / d]; +} + +function arrow(data, {x, x1, x2, y, y1, y2, ...options} = {}) { + [x1, x2] = maybeSameValue(x, x1, x2); + [y1, y2] = maybeSameValue(y, y1, y2); + return new Arrow(data, {...options, x1, x2, y1, y2}); +} + +class AbstractBar extends Mark { + constructor(data, channels, options = {}, defaults) { + super(data, channels, options, defaults); + const {inset = 0, insetTop = inset, insetRight = inset, insetBottom = inset, insetLeft = inset, rx, ry} = options; + this.insetTop = number$1(insetTop); + this.insetRight = number$1(insetRight); + this.insetBottom = number$1(insetBottom); + this.insetLeft = number$1(insetLeft); + this.rx = impliedString(rx, "auto"); // number or percentage + this.ry = impliedString(ry, "auto"); + } + render(index, scales, channels, dimensions, context) { + const {rx, ry} = this; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(this._transform, this, scales) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("rect") + .call(applyDirectStyles, this) + .attr("x", this._x(scales, channels, dimensions)) + .attr("width", this._width(scales, channels, dimensions)) + .attr("y", this._y(scales, channels, dimensions)) + .attr("height", this._height(scales, channels, dimensions)) + .call(applyAttr, "rx", rx) + .call(applyAttr, "ry", ry) + .call(applyChannelStyles, this, channels) + ) + .node(); + } + _x(scales, {x: X}, {marginLeft}) { + const {insetLeft} = this; + return X ? (i) => X[i] + insetLeft : marginLeft + insetLeft; + } + _y(scales, {y: Y}, {marginTop}) { + const {insetTop} = this; + return Y ? (i) => Y[i] + insetTop : marginTop + insetTop; + } + _width({x}, {x: X}, {marginRight, marginLeft, width}) { + const {insetLeft, insetRight} = this; + const bandwidth = X && x ? x.bandwidth() : width - marginRight - marginLeft; + return Math.max(0, bandwidth - insetLeft - insetRight); + } + _height({y}, {y: Y}, {marginTop, marginBottom, height}) { + const {insetTop, insetBottom} = this; + const bandwidth = Y && y ? y.bandwidth() : height - marginTop - marginBottom; + return Math.max(0, bandwidth - insetTop - insetBottom); + } +} + +const defaults$d = { + ariaLabel: "bar" +}; + +class BarX extends AbstractBar { + constructor(data, options = {}) { + const {x1, x2, y} = options; + super( + data, + { + x1: {value: x1, scale: "x"}, + x2: {value: x2, scale: "x"}, + y: {value: y, scale: "y", type: "band", optional: true} + }, + options, + defaults$d + ); + } + _transform(selection, mark, {x}) { + selection.call(applyTransform, mark, {x}, 0, 0); + } + _x({x}, {x1: X1, x2: X2}, {marginLeft}) { + const {insetLeft} = this; + return isCollapsed(x) ? marginLeft + insetLeft : (i) => Math.min(X1[i], X2[i]) + insetLeft; + } + _width({x}, {x1: X1, x2: X2}, {marginRight, marginLeft, width}) { + const {insetLeft, insetRight} = this; + return isCollapsed(x) + ? width - marginRight - marginLeft - insetLeft - insetRight + : (i) => Math.max(0, Math.abs(X2[i] - X1[i]) - insetLeft - insetRight); + } +} + +class BarY extends AbstractBar { + constructor(data, options = {}) { + const {x, y1, y2} = options; + super( + data, + { + y1: {value: y1, scale: "y"}, + y2: {value: y2, scale: "y"}, + x: {value: x, scale: "x", type: "band", optional: true} + }, + options, + defaults$d + ); + } + _transform(selection, mark, {y}) { + selection.call(applyTransform, mark, {y}, 0, 0); + } + _y({y}, {y1: Y1, y2: Y2}, {marginTop}) { + const {insetTop} = this; + return isCollapsed(y) ? marginTop + insetTop : (i) => Math.min(Y1[i], Y2[i]) + insetTop; + } + _height({y}, {y1: Y1, y2: Y2}, {marginTop, marginBottom, height}) { + const {insetTop, insetBottom} = this; + return isCollapsed(y) + ? height - marginTop - marginBottom - insetTop - insetBottom + : (i) => Math.max(0, Math.abs(Y2[i] - Y1[i]) - insetTop - insetBottom); + } +} + +function barX(data, options = {}) { + if (!hasXY(options)) options = {...options, y: indexOf, x2: identity$1}; + return new BarX(data, maybeStackX(maybeIntervalX(maybeIdentityX(options)))); +} + +function barY(data, options = {}) { + if (!hasXY(options)) options = {...options, x: indexOf, y2: identity$1}; + return new BarY(data, maybeStackY(maybeIntervalY(maybeIdentityY(options)))); +} + +const defaults$c = { + ariaLabel: "cell" +}; + +class Cell extends AbstractBar { + constructor(data, {x, y, ...options} = {}) { + super( + data, + { + x: {value: x, scale: "x", type: "band", optional: true}, + y: {value: y, scale: "y", type: "band", optional: true} + }, + options, + defaults$c + ); + } + _transform(selection, mark) { + // apply dx, dy + selection.call(applyTransform, mark, {}, 0, 0); + } +} + +function cell(data, {x, y, ...options} = {}) { + [x, y] = maybeTuple(x, y); + return new Cell(data, {...options, x, y}); +} + +function cellX(data, {x = indexOf, fill, stroke, ...options} = {}) { + if (fill === undefined && maybeColorChannel(stroke)[0] === undefined) fill = identity$1; + return new Cell(data, {...options, x, fill, stroke}); +} + +function cellY(data, {y = indexOf, fill, stroke, ...options} = {}) { + if (fill === undefined && maybeColorChannel(stroke)[0] === undefined) fill = identity$1; + return new Cell(data, {...options, y, fill, stroke}); +} + +const defaults$b = { + ariaLabel: "dot", + fill: "none", + stroke: "currentColor", + strokeWidth: 1.5 +}; + +function withDefaultSort(options) { + return options.sort === undefined && options.reverse === undefined ? sort({channel: "-r"}, options) : options; +} + +class Dot extends Mark { + constructor(data, options = {}) { + const {x, y, r, rotate, symbol = d3.symbolCircle, frameAnchor} = options; + const [vrotate, crotate] = maybeNumberChannel(rotate, 0); + const [vsymbol, csymbol] = maybeSymbolChannel(symbol); + const [vr, cr] = maybeNumberChannel(r, vsymbol == null ? 3 : 4.5); + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + r: {value: vr, scale: "r", filter: positive, optional: true}, + rotate: {value: vrotate, optional: true}, + symbol: {value: vsymbol, scale: "auto", optional: true} + }, + withDefaultSort(options), + defaults$b + ); + this.r = cr; + this.rotate = crotate; + this.symbol = csymbol; + this.frameAnchor = maybeFrameAnchor(frameAnchor); + + // Give a hint to the symbol scale; this allows the symbol scale to choose + // appropriate default symbols based on whether the dots are filled or + // stroked, and for the symbol legend to match the appearance of the dots. + const {channels} = this; + const {symbol: symbolChannel} = channels; + if (symbolChannel) { + const {fill: fillChannel, stroke: strokeChannel} = channels; + symbolChannel.hint = { + fill: fillChannel + ? fillChannel.value === symbolChannel.value + ? "color" + : "currentColor" + : this.fill ?? "currentColor", + stroke: strokeChannel + ? strokeChannel.value === symbolChannel.value + ? "color" + : "currentColor" + : this.stroke ?? "none" + }; + } + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x: X, y: Y, r: R, rotate: A, symbol: S} = channels; + const {r, rotate, symbol} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const circle = symbol === d3.symbolCircle; + const size = R ? undefined : r * r * Math.PI; + if (negative(r)) index = []; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {x: X && x, y: Y && y}) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append(circle ? "circle" : "path") + .call(applyDirectStyles, this) + .call( + circle + ? (selection) => { + selection + .attr("cx", X ? (i) => X[i] : cx) + .attr("cy", Y ? (i) => Y[i] : cy) + .attr("r", R ? (i) => R[i] : r); + } + : (selection) => { + selection + .attr( + "transform", + template`translate(${X ? (i) => X[i] : cx},${Y ? (i) => Y[i] : cy})${ + A ? (i) => ` rotate(${A[i]})` : rotate ? ` rotate(${rotate})` : `` + }` + ) + .attr( + "d", + R && S + ? (i) => { + const p = d3.pathRound(); + S[i].draw(p, R[i] * R[i] * Math.PI); + return p; + } + : R + ? (i) => { + const p = d3.pathRound(); + symbol.draw(p, R[i] * R[i] * Math.PI); + return p; + } + : S + ? (i) => { + const p = d3.pathRound(); + S[i].draw(p, size); + return p; + } + : (() => { + const p = d3.pathRound(); + symbol.draw(p, size); + return p; + })() + ); + } + ) + .call(applyChannelStyles, this, channels) + ) + .node(); + } +} + +function dot(data, {x, y, ...options} = {}) { + if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); + return new Dot(data, {...options, x, y}); +} + +function dotX(data, {x = identity$1, ...options} = {}) { + return new Dot(data, maybeIntervalMidY({...options, x})); +} + +function dotY(data, {y = identity$1, ...options} = {}) { + return new Dot(data, maybeIntervalMidX({...options, y})); +} + +function circle(data, options) { + return dot(data, {...options, symbol: "circle"}); +} + +function hexagon(data, options) { + return dot(data, {...options, symbol: "hexagon"}); +} + +const defaults$a = { + ariaLabel: "line", + fill: "none", + stroke: "currentColor", + strokeWidth: 1.5, + strokeLinecap: "round", + strokeLinejoin: "round", + strokeMiterlimit: 1 +}; + +class Line extends Mark { + constructor(data, options = {}) { + const {x, y, z, curve, tension} = options; + super( + data, + { + x: {value: x, scale: "x"}, + y: {value: y, scale: "y"}, + z: {value: maybeZ(options), optional: true} + }, + options, + defaults$a + ); + this.z = z; + this.curve = maybeCurveAuto(curve, tension); + markers(this, options); + } + filter(index) { + return index; + } + project(channels, values, context) { + // For the auto curve, projection is handled at render. + if (this.curve !== curveAuto) { + super.project(channels, values, context); + } + } + render(index, scales, channels, dimensions, context) { + const {x: X, y: Y} = channels; + const {curve} = this; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, scales) + .call((g) => + g + .selectAll() + .data(groupIndex(index, [X, Y], this, channels)) + .enter() + .append("path") + .call(applyDirectStyles, this) + .call(applyGroupedChannelStyles, this, channels) + .call(applyGroupedMarkers, this, channels, context) + .attr( + "d", + curve === curveAuto && context.projection + ? sphereLine(context.projection, X, Y) + : d3.line() + .curve(curve) + .defined((i) => i >= 0) + .x((i) => X[i]) + .y((i) => Y[i]) + ) + ) + .node(); + } +} + +function sphereLine(projection, X, Y) { + const path = d3.geoPath(projection); + X = coerceNumbers(X); + Y = coerceNumbers(Y); + return (I) => { + let line = []; + const lines = [line]; + for (const i of I) { + // Check for undefined value; see groupIndex. + if (i === -1) { + line = []; + lines.push(line); + } else { + line.push([X[i], Y[i]]); + } + } + return path({type: "MultiLineString", coordinates: lines}); + }; +} + +function line(data, {x, y, ...options} = {}) { + [x, y] = maybeTuple(x, y); + return new Line(data, {...options, x, y}); +} + +function lineX(data, {x = identity$1, y = indexOf, ...options} = {}) { + return new Line(data, maybeDenseIntervalY({...options, x, y})); +} + +function lineY(data, {x = indexOf, y = identity$1, ...options} = {}) { + return new Line(data, maybeDenseIntervalX({...options, x, y})); +} + +const defaults$9 = { + ariaLabel: "rect" +}; + +class Rect extends Mark { + constructor(data, options = {}) { + const { + x1, + y1, + x2, + y2, + inset = 0, + insetTop = inset, + insetRight = inset, + insetBottom = inset, + insetLeft = inset, + rx, + ry + } = options; + super( + data, + { + x1: {value: x1, scale: "x", type: x1 != null && x2 == null ? "band" : undefined, optional: true}, + y1: {value: y1, scale: "y", type: y1 != null && y2 == null ? "band" : undefined, optional: true}, + x2: {value: x2, scale: "x", optional: true}, + y2: {value: y2, scale: "y", optional: true} + }, + options, + defaults$9 + ); + this.insetTop = number$1(insetTop); + this.insetRight = number$1(insetRight); + this.insetBottom = number$1(insetBottom); + this.insetLeft = number$1(insetLeft); + this.rx = impliedString(rx, "auto"); // number or percentage + this.ry = impliedString(ry, "auto"); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x1: X1, y1: Y1, x2: X2, y2: Y2} = channels; + const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions; + const {projection} = context; + const {insetTop, insetRight, insetBottom, insetLeft, rx, ry} = this; + const bx = (x?.bandwidth ? x.bandwidth() : 0) - insetLeft - insetRight; + const by = (y?.bandwidth ? y.bandwidth() : 0) - insetTop - insetBottom; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {}, 0, 0) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("rect") + .call(applyDirectStyles, this) + .attr( + "x", + X1 && (projection || !isCollapsed(x)) + ? X2 + ? (i) => Math.min(X1[i], X2[i]) + insetLeft + : (i) => X1[i] + insetLeft + : marginLeft + insetLeft + ) + .attr( + "y", + Y1 && (projection || !isCollapsed(y)) + ? Y2 + ? (i) => Math.min(Y1[i], Y2[i]) + insetTop + : (i) => Y1[i] + insetTop + : marginTop + insetTop + ) + .attr( + "width", + X1 && (projection || !isCollapsed(x)) + ? X2 + ? (i) => Math.max(0, Math.abs(X2[i] - X1[i]) + bx) + : bx + : width - marginRight - marginLeft - insetRight - insetLeft + ) + .attr( + "height", + Y1 && (projection || !isCollapsed(y)) + ? Y2 + ? (i) => Math.max(0, Math.abs(Y1[i] - Y2[i]) + by) + : by + : height - marginTop - marginBottom - insetTop - insetBottom + ) + .call(applyAttr, "rx", rx) + .call(applyAttr, "ry", ry) + .call(applyChannelStyles, this, channels) + ) + .node(); + } +} + +function rect(data, options) { + return new Rect(data, maybeTrivialIntervalX(maybeTrivialIntervalY(options))); +} + +function rectX(data, options = {}) { + if (!hasXY(options)) options = {...options, y: indexOf, x2: identity$1, interval: 1}; + return new Rect(data, maybeStackX(maybeTrivialIntervalY(maybeIdentityX(options)))); +} + +function rectY(data, options = {}) { + if (!hasXY(options)) options = {...options, x: indexOf, y2: identity$1, interval: 1}; + return new Rect(data, maybeStackY(maybeTrivialIntervalX(maybeIdentityY(options)))); +} + +function autoSpec(data, options) { + options = normalizeOptions(options); + + // Greedily materialize columns for type inference; we’ll need them anyway to + // plot! Note that we don’t apply any type inference to the fx and fy + // channels, if present; these are always ordinal (at least for now). + const {x, y, color, size} = options; + const X = materializeValue(data, x); + const Y = materializeValue(data, y); + const C = materializeValue(data, color); + const S = materializeValue(data, size); + + // Compute the default options. + let { + fx, + fy, + x: {value: xValue, reduce: xReduce, zero: xZero, ...xOptions}, + y: {value: yValue, reduce: yReduce, zero: yZero, ...yOptions}, + color: {value: colorValue, color: colorColor, reduce: colorReduce}, + size: {value: sizeValue, reduce: sizeReduce}, // TODO constant radius? + mark + } = options; + + // Determine the default reducer, if any. + if (xReduce === undefined) + xReduce = yReduce == null && xValue == null && sizeValue == null && yValue != null ? "count" : null; + if (yReduce === undefined) + yReduce = xReduce == null && yValue == null && sizeValue == null && xValue != null ? "count" : null; + + // Determine the default size reducer, if any. + if ( + sizeReduce === undefined && + sizeValue == null && + colorReduce == null && + xReduce == null && + yReduce == null && + (xValue == null || isOrdinal(X)) && + (yValue == null || isOrdinal(Y)) + ) { + sizeReduce = "count"; + } + + // Determine the default zero-ness. + if (xZero === undefined) xZero = isZeroReducer(xReduce) ? true : undefined; + if (yZero === undefined) yZero = isZeroReducer(yReduce) ? true : undefined; + + // TODO Shorthand: array of primitives should result in a histogram + if (xValue == null && yValue == null) throw new Error("must specify x or y"); + if (xReduce != null && yValue == null) throw new Error("reducing x requires y"); + if (yReduce != null && xValue == null) throw new Error("reducing y requires x"); + + // Determine the default mark type. + if (mark === undefined) { + mark = + sizeValue != null || sizeReduce != null + ? "dot" + : isZeroReducer(xReduce) || isZeroReducer(yReduce) || colorReduce != null // histogram or heatmap + ? "bar" + : xValue != null && yValue != null + ? isOrdinal(X) || isOrdinal(Y) || (xReduce == null && yReduce == null && !isMonotonic(X) && !isMonotonic(Y)) + ? "dot" + : "line" + : xValue != null || yValue != null + ? "rule" + : null; + } + + let Z; // may be set to null to disable series-by-color for line and area + let colorMode; // "fill" or "stroke" + + // Determine the mark implementation. + let markImpl; + switch (mark) { + case "dot": + markImpl = dot; + colorMode = "stroke"; + break; + case "line": + markImpl = + (X && Y) || xReduce != null || yReduce != null // same logic as area (see below), but default to line + ? yZero || yReduce != null || (X && isMonotonic(X)) + ? lineY + : xZero || xReduce != null || (Y && isMonotonic(Y)) + ? lineX + : line + : X // 1d line by index + ? lineX + : lineY; + colorMode = "stroke"; + if (isHighCardinality(C)) Z = null; // TODO only if z not set by user + break; + case "area": + markImpl = !(yZero || yReduce != null) && (xZero || xReduce != null || (Y && isMonotonic(Y))) ? areaX : areaY; // favor areaY if unsure + colorMode = "fill"; + if (isHighCardinality(C)) Z = null; // TODO only if z not set by user + break; + case "rule": + markImpl = X ? ruleX : ruleY; + colorMode = "stroke"; + break; + case "bar": + markImpl = + xReduce != null // bin or group on y + ? isOrdinal(Y) + ? isSelectReducer(xReduce) && X && isOrdinal(X) + ? cell + : barX + : rectX + : yReduce != null // bin or group on x + ? isOrdinal(X) + ? isSelectReducer(yReduce) && Y && isOrdinal(Y) + ? cell + : barY + : rectY + : colorReduce != null || sizeReduce != null // bin or group on both x and y + ? X && isOrdinal(X) && Y && isOrdinal(Y) + ? cell + : X && isOrdinal(X) + ? barY + : Y && isOrdinal(Y) + ? barX + : rect + : X && isNumeric(X) && !(Y && isNumeric(Y)) + ? barX // if y is temporal, treat as ordinal + : Y && isNumeric(Y) && !(X && isNumeric(X)) + ? barY // if x is temporal, treat as ordinal + : cell; + colorMode = "fill"; + break; + default: + throw new Error(`invalid mark: ${mark}`); + } + + // Determine the mark options. + let markOptions = { + fx, + fy, + x: X ?? undefined, // treat null x as undefined for implicit stack + y: Y ?? undefined, // treat null y as undefined for implicit stack + [colorMode]: C ?? colorColor, + z: Z, + r: S ?? undefined, // treat null size as undefined for default constant radius + tip: true + }; + let transformImpl; + let transformOptions = {[colorMode]: colorReduce ?? undefined, r: sizeReduce ?? undefined}; + if (xReduce != null && yReduce != null) { + throw new Error(`cannot reduce both x and y`); // for now at least + } else if (yReduce != null) { + transformOptions.y = yReduce; + transformImpl = isOrdinal(X) ? groupX : binX; + } else if (xReduce != null) { + transformOptions.x = xReduce; + transformImpl = isOrdinal(Y) ? groupY : binY; + } else if (colorReduce != null || sizeReduce != null) { + if (X && Y) { + transformImpl = isOrdinal(X) && isOrdinal(Y) ? group : isOrdinal(X) ? binY : isOrdinal(Y) ? binX : bin; + } else if (X) { + transformImpl = isOrdinal(X) ? groupX : binX; + } else if (Y) { + transformImpl = isOrdinal(Y) ? groupY : binY; + } + } + + // When using the bin transform, pass through additional options (e.g., thresholds). + if (transformImpl === bin || transformImpl === binX) markOptions.x = {value: X, ...xOptions}; + if (transformImpl === bin || transformImpl === binY) markOptions.y = {value: Y, ...yOptions}; + + // If zero-ness is not specified, default based on whether the resolved mark + // type will include a zero baseline. + if (xZero === undefined) + xZero = + X && + !(transformImpl === bin || transformImpl === binX) && + (markImpl === barX || markImpl === areaX || markImpl === rectX || markImpl === ruleY); + if (yZero === undefined) + yZero = + Y && + !(transformImpl === bin || transformImpl === binY) && + (markImpl === barY || markImpl === areaY || markImpl === rectY || markImpl === ruleX); + + return { + fx: fx ?? null, + fy: fy ?? null, + x: { + value: xValue ?? null, + reduce: xReduce ?? null, + zero: !!xZero, + ...xOptions + }, + y: { + value: yValue ?? null, + reduce: yReduce ?? null, + zero: !!yZero, + ...yOptions + }, + color: { + value: colorValue ?? null, + reduce: colorReduce ?? null, + ...(colorColor !== undefined && {color: colorColor}) + }, + size: { + value: sizeValue ?? null, + reduce: sizeReduce ?? null + }, + mark, + markImpl: implNames[markImpl], + markOptions, + transformImpl: implNames[transformImpl], + transformOptions, + colorMode + }; +} + +function auto(data, options) { + const spec = autoSpec(data, options); + const { + fx, + fy, + x: {zero: xZero}, + y: {zero: yZero}, + markOptions, + transformOptions, + colorMode + } = spec; + const markImpl = impls[spec.markImpl]; + const transformImpl = impls[spec.transformImpl]; + // In the case of filled marks (particularly bars and areas) the frame and + // rules should come after the mark; in the case of stroked marks + // (particularly dots and lines) they should come before the mark. + const frames = fx != null || fy != null ? frame({strokeOpacity: 0.1}) : null; + const rules = [xZero ? ruleX([0]) : null, yZero ? ruleY([0]) : null]; + const mark = markImpl(data, transformImpl ? transformImpl(transformOptions, markOptions) : markOptions); + return colorMode === "stroke" ? marks(frames, rules, mark) : marks(frames, mark, rules); +} + +// TODO What about sorted within series? +function isMonotonic(values) { + let previous; + let previousOrder; + for (const value of values) { + if (value == null) continue; + if (previous === undefined) { + previous = value; + continue; + } + const order = Math.sign(d3.ascending(previous, value)); + if (!order) continue; // skip zero, NaN + if (previousOrder !== undefined && order !== previousOrder) return false; + previous = value; + previousOrder = order; + } + return true; +} + +// Allow x and y and other dimensions to be specified as shorthand field names +// (but note that they can also be specified as a {transform} object such as +// Plot.identity). We don’t support reducers for the faceting, but for symmetry +// with x and y we allow facets to be specified as {value} objects. +function normalizeOptions({x, y, color, size, fx, fy, mark} = {}) { + if (!isOptions(x)) x = makeOptions(x); + if (!isOptions(y)) y = makeOptions(y); + if (!isOptions(color)) color = isColor(color) ? {color} : makeOptions(color); + if (!isOptions(size)) size = makeOptions(size); + if (isOptions(fx)) ({value: fx} = makeOptions(fx)); + if (isOptions(fy)) ({value: fy} = makeOptions(fy)); + if (mark != null) mark = `${mark}`.toLowerCase(); + return {x, y, color, size, fx, fy, mark}; +} + +// To apply heuristics based on the data types (values), realize the columns. We +// could maybe look at the data.schema here, but Plot’s behavior depends on the +// actual values anyway, so this probably is what we want. +function materializeValue(data, options) { + const V = valueof(data, options.value); + if (V) V.label = labelof(options.value); + return V; +} + +function makeOptions(value) { + return isReducer(value) ? {reduce: value} : {value}; +} + +// The distinct, count, sum, and proportion reducers are additive (stackable). +function isZeroReducer(reduce) { + return /^(?:distinct|count|sum|proportion)$/i.test(reduce); +} + +// The first, last, and mode reducers preserve the type of the aggregated values. +function isSelectReducer(reduce) { + return /^(?:first|last|mode)$/i.test(reduce); +} + +// https://github.com/observablehq/plot/blob/818562649280e155136f730fc496e0b3d15ae464/src/transforms/group.js#L236 +function isReducer(reduce) { + if (reduce == null) return false; + if (typeof reduce.reduceIndex === "function") return true; + if (typeof reduce.reduce === "function" && isObject(reduce)) return true; // N.B. array.reduce + if (/^p\d{2}$/i.test(reduce)) return true; + switch (`${reduce}`.toLowerCase()) { + case "first": + case "last": + case "count": + case "distinct": + case "sum": + case "proportion": + case "proportion-facet": // TODO remove me? + case "deviation": + case "min": + case "min-index": // TODO remove me? + case "max": + case "max-index": // TODO remove me? + case "mean": + case "median": + case "variance": + case "mode": + // These are technically reducers, but I think we’d want to treat them as fields? + // case "x": + // case "x1": + // case "x2": + // case "y": + // case "y1": + // case "y2": + return true; + } + return false; +} + +function isHighCardinality(value) { + return value ? new d3.InternSet(value).size > value.length >> 1 : false; +} + +const impls = { + dot, + line, + lineX, + lineY, + areaX, + areaY, + ruleX, + ruleY, + barX, + barY, + rect, + rectX, + rectY, + cell, + bin, + binX, + binY, + group, + groupX, + groupY +}; + +// Instead of returning the mark or transform implementation directly, we return +// the implementation name to facilitate code compilation (“eject to explicit +// marks”). An implementation-to-name mapping needs to live somewhere for +// compilation, and by having it in Plot we can more easily introduce a new mark +// or transform implementation in Plot.auto without having to synchronize a +// downstream change in the compiler. +const implNames = Object.fromEntries(Object.entries(impls).map(([name, impl]) => [impl, name])); + +function mapX(mapper, options = {}) { + let {x, x1, x2} = options; + if (x === undefined && x1 === undefined && x2 === undefined) options = {...options, x: (x = identity$1)}; + const outputs = {}; + if (x != null) outputs.x = mapper; + if (x1 != null) outputs.x1 = mapper; + if (x2 != null) outputs.x2 = mapper; + return map(outputs, options); +} + +function mapY(mapper, options = {}) { + let {y, y1, y2} = options; + if (y === undefined && y1 === undefined && y2 === undefined) options = {...options, y: (y = identity$1)}; + const outputs = {}; + if (y != null) outputs.y = mapper; + if (y1 != null) outputs.y1 = mapper; + if (y2 != null) outputs.y2 = mapper; + return map(outputs, options); +} + +function map(outputs = {}, options = {}) { + const z = maybeZ(options); + const channels = Object.entries(outputs).map(([key, map]) => { + const input = maybeInput(key, options); + if (input == null) throw new Error(`missing channel: ${key}`); + const [output, setOutput] = column(input); + return {key, input, output, setOutput, map: maybeMap(map)}; + }); + return { + ...basic(options, (data, facets) => { + const Z = valueof(data, z); + const X = channels.map(({input}) => valueof(data, input)); + const MX = channels.map(({setOutput}) => setOutput(new Array(data.length))); + for (const facet of facets) { + for (const I of Z ? d3.group(facet, (i) => Z[i]).values() : [facet]) { + channels.forEach(({map}, i) => map.mapIndex(I, X[i], MX[i])); + } + } + return {data, facets}; + }), + ...Object.fromEntries(channels.map(({key, output}) => [key, output])) + }; +} + +function maybeMap(map) { + if (map == null) throw new Error("missing map"); + if (typeof map.mapIndex === "function") return map; + if (typeof map.map === "function" && isObject(map)) return mapMap(map); // N.B. array.map + if (typeof map === "function") return mapFunction(taker(map)); + switch (`${map}`.toLowerCase()) { + case "cumsum": + return mapCumsum; + case "rank": + return mapFunction((I, V) => d3.rank(I, (i) => V[i])); + case "quantile": + return mapFunction((I, V) => rankQuantile(I, (i) => V[i])); + } + throw new Error(`invalid map: ${map}`); +} + +function mapMap(map) { + console.warn("deprecated map interface; implement mapIndex instead."); + return {mapIndex: map.map.bind(map)}; +} + +function rankQuantile(I, f) { + const n = d3.count(I, f) - 1; + return d3.rank(I, f).map((r) => r / n); +} + +function mapFunction(f) { + return { + mapIndex(I, S, T) { + const M = f(I, S); + if (M.length !== I.length) throw new Error("map function returned a mismatched length"); + for (let i = 0, n = I.length; i < n; ++i) T[I[i]] = M[i]; + } + }; +} + +const mapCumsum = { + mapIndex(I, S, T) { + let sum = 0; + for (const i of I) T[i] = sum += S[i]; + } +}; + +function windowX(windowOptions = {}, options) { + if (arguments.length === 1) options = windowOptions; + return mapX(window$1(windowOptions), options); +} + +function windowY(windowOptions = {}, options) { + if (arguments.length === 1) options = windowOptions; + return mapY(window$1(windowOptions), options); +} + +function window$1(options = {}) { + if (typeof options === "number") options = {k: options}; + let {k, reduce, shift, anchor, strict} = options; + if (anchor === undefined && shift !== undefined) { + anchor = maybeShift(shift); + warn(`Warning: the shift option is deprecated; please use anchor "${anchor}" instead.`); + } + if (!((k = Math.floor(k)) > 0)) throw new Error(`invalid k: ${k}`); + return maybeReduce(reduce)(k, maybeAnchor$1(anchor, k), strict); +} + +function maybeAnchor$1(anchor = "middle", k) { + switch (`${anchor}`.toLowerCase()) { + case "middle": + return (k - 1) >> 1; + case "start": + return 0; + case "end": + return k - 1; + } + throw new Error(`invalid anchor: ${anchor}`); +} + +function maybeShift(shift) { + switch (`${shift}`.toLowerCase()) { + case "centered": + return "middle"; + case "leading": + return "start"; + case "trailing": + return "end"; + } + throw new Error(`invalid shift: ${shift}`); +} + +function maybeReduce(reduce = "mean") { + if (typeof reduce === "string") { + if (/^p\d{2}$/i.test(reduce)) return reduceAccessor(percentile(reduce)); + switch (reduce.toLowerCase()) { + case "deviation": + return reduceAccessor(d3.deviation); + case "max": + return reduceArray((I, V) => d3.max(I, (i) => V[i])); + case "mean": + return reduceMean; + case "median": + return reduceAccessor(d3.median); + case "min": + return reduceArray((I, V) => d3.min(I, (i) => V[i])); + case "mode": + return reduceArray((I, V) => d3.mode(I, (i) => V[i])); + case "sum": + return reduceSum; + case "variance": + return reduceAccessor(d3.variance); + case "difference": + return reduceDifference; + case "ratio": + return reduceRatio; + case "first": + return reduceFirst; + case "last": + return reduceLast; + } + } + if (typeof reduce !== "function") throw new Error(`invalid reduce: ${reduce}`); + return reduceArray(taker(reduce)); +} + +// Note that the subarray may include NaN in the non-strict case; we expect the +// function f to handle that itself (e.g., by filtering as needed). The D3 +// reducers (e.g., min, max, mean, median) do, and it’s faster to avoid +// redundant filtering. +function reduceAccessor(f) { + return (k, s, strict) => + strict + ? { + mapIndex(I, S, T) { + const v = (i) => (S[i] == null ? NaN : +S[i]); + let nans = 0; + for (let i = 0; i < k - 1; ++i) if (isNaN(v(i))) ++nans; + for (let i = 0, n = I.length - k + 1; i < n; ++i) { + if (isNaN(v(i + k - 1))) ++nans; + T[I[i + s]] = nans === 0 ? f(subarray(I, i, i + k), v) : NaN; + if (isNaN(v(i))) --nans; + } + } + } + : { + mapIndex(I, S, T) { + const v = (i) => (S[i] == null ? NaN : +S[i]); + for (let i = -s; i < 0; ++i) { + T[I[i + s]] = f(subarray(I, 0, i + k), v); + } + for (let i = 0, n = I.length - s; i < n; ++i) { + T[I[i + s]] = f(subarray(I, i, i + k), v); + } + } + }; +} + +function reduceArray(f) { + return (k, s, strict) => + strict + ? { + mapIndex(I, S, T) { + let count = 0; + for (let i = 0; i < k - 1; ++i) count += defined(S[I[i]]); + for (let i = 0, n = I.length - k + 1; i < n; ++i) { + count += defined(S[I[i + k - 1]]); + if (count === k) T[I[i + s]] = f(subarray(I, i, i + k), S); + count -= defined(S[I[i]]); + } + } + } + : { + mapIndex(I, S, T) { + for (let i = -s; i < 0; ++i) { + T[I[i + s]] = f(subarray(I, 0, i + k), S); + } + for (let i = 0, n = I.length - s; i < n; ++i) { + T[I[i + s]] = f(subarray(I, i, i + k), S); + } + } + }; +} + +function reduceSum(k, s, strict) { + return strict + ? { + mapIndex(I, S, T) { + let nans = 0; + let sum = 0; + for (let i = 0; i < k - 1; ++i) { + const v = S[I[i]]; + if (v === null || isNaN(v)) ++nans; + else sum += +v; + } + for (let i = 0, n = I.length - k + 1; i < n; ++i) { + const a = S[I[i]]; + const b = S[I[i + k - 1]]; + if (b === null || isNaN(b)) ++nans; + else sum += +b; + T[I[i + s]] = nans === 0 ? sum : NaN; + if (a === null || isNaN(a)) --nans; + else sum -= +a; + } + } + } + : { + mapIndex(I, S, T) { + let sum = 0; + const n = I.length; + for (let i = 0, j = Math.min(n, k - s - 1); i < j; ++i) { + sum += +S[I[i]] || 0; + } + for (let i = -s, j = n - s; i < j; ++i) { + sum += +S[I[i + k - 1]] || 0; + T[I[i + s]] = sum; + sum -= +S[I[i]] || 0; + } + } + }; +} + +function reduceMean(k, s, strict) { + if (strict) { + const sum = reduceSum(k, s, strict); + return { + mapIndex(I, S, T) { + sum.mapIndex(I, S, T); + for (let i = 0, n = I.length - k + 1; i < n; ++i) { + T[I[i + s]] /= k; + } + } + }; + } else { + return { + mapIndex(I, S, T) { + let sum = 0; + let count = 0; + const n = I.length; + for (let i = 0, j = Math.min(n, k - s - 1); i < j; ++i) { + let v = S[I[i]]; + if (v !== null && !isNaN((v = +v))) (sum += v), ++count; + } + for (let i = -s, j = n - s; i < j; ++i) { + let a = S[I[i + k - 1]]; + let b = S[I[i]]; + if (a !== null && !isNaN((a = +a))) (sum += a), ++count; + T[I[i + s]] = sum / count; + if (b !== null && !isNaN((b = +b))) (sum -= b), --count; + } + } + }; + } +} + +function firstDefined(S, I, i, k) { + for (let j = i + k; i < j; ++i) { + const v = S[I[i]]; + if (defined(v)) return v; + } +} + +function lastDefined(S, I, i, k) { + for (let j = i + k - 1; j >= i; --j) { + const v = S[I[j]]; + if (defined(v)) return v; + } +} + +function firstNumber(S, I, i, k) { + for (let j = i + k; i < j; ++i) { + let v = S[I[i]]; + if (v !== null && !isNaN((v = +v))) return v; + } +} + +function lastNumber(S, I, i, k) { + for (let j = i + k - 1; j >= i; --j) { + let v = S[I[j]]; + if (v !== null && !isNaN((v = +v))) return v; + } +} + +function reduceDifference(k, s, strict) { + return strict + ? { + mapIndex(I, S, T) { + for (let i = 0, n = I.length - k; i < n; ++i) { + const a = S[I[i]]; + const b = S[I[i + k - 1]]; + T[I[i + s]] = a === null || b === null ? NaN : b - a; + } + } + } + : { + mapIndex(I, S, T) { + for (let i = -s, n = I.length - k + s + 1; i < n; ++i) { + T[I[i + s]] = lastNumber(S, I, i, k) - firstNumber(S, I, i, k); + } + } + }; +} + +function reduceRatio(k, s, strict) { + return strict + ? { + mapIndex(I, S, T) { + for (let i = 0, n = I.length - k; i < n; ++i) { + const a = S[I[i]]; + const b = S[I[i + k - 1]]; + T[I[i + s]] = a === null || b === null ? NaN : b / a; + } + } + } + : { + mapIndex(I, S, T) { + for (let i = -s, n = I.length - k + s + 1; i < n; ++i) { + T[I[i + s]] = lastNumber(S, I, i, k) / firstNumber(S, I, i, k); + } + } + }; +} + +function reduceFirst(k, s, strict) { + return strict + ? { + mapIndex(I, S, T) { + for (let i = 0, n = I.length - k; i < n; ++i) { + T[I[i + s]] = S[I[i]]; + } + } + } + : { + mapIndex(I, S, T) { + for (let i = -s, n = I.length - k + s + 1; i < n; ++i) { + T[I[i + s]] = firstDefined(S, I, i, k); + } + } + }; +} + +function reduceLast(k, s, strict) { + return strict + ? { + mapIndex(I, S, T) { + for (let i = 0, n = I.length - k; i < n; ++i) { + T[I[i + s]] = S[I[i + k - 1]]; + } + } + } + : { + mapIndex(I, S, T) { + for (let i = -s, n = I.length - k + s + 1; i < n; ++i) { + T[I[i + s]] = lastDefined(S, I, i, k); + } + } + }; +} + +const defaults$8 = { + n: 20, + k: 2, + color: "currentColor", + opacity: 0.2, + strict: true, + anchor: "end" +}; + +function bollingerX( + data, + { + x = identity$1, + y, + k = defaults$8.k, + color = defaults$8.color, + opacity = defaults$8.opacity, + fill = color, + fillOpacity = opacity, + stroke = color, + strokeOpacity, + strokeWidth, + ...options + } = {} +) { + return marks( + isNoneish(fill) + ? null + : areaX( + data, + map( + {x1: bollinger({k: -k, ...options}), x2: bollinger({k, ...options})}, + {x1: x, x2: x, y, fill, fillOpacity, ...options} + ) + ), + isNoneish(stroke) + ? null + : lineX(data, map({x: bollinger(options)}, {x, y, stroke, strokeOpacity, strokeWidth, ...options})) + ); +} + +function bollingerY( + data, + { + x, + y = identity$1, + k = defaults$8.k, + color = defaults$8.color, + opacity = defaults$8.opacity, + fill = color, + fillOpacity = opacity, + stroke = color, + strokeOpacity, + strokeWidth, + ...options + } = {} +) { + return marks( + isNoneish(fill) + ? null + : areaY( + data, + map( + {y1: bollinger({k: -k, ...options}), y2: bollinger({k, ...options})}, + {x, y1: y, y2: y, fill, fillOpacity, ...options} + ) + ), + isNoneish(stroke) + ? null + : lineY(data, map({y: bollinger(options)}, {x, y, stroke, strokeOpacity, strokeWidth, ...options})) + ); +} + +function bollinger({n = defaults$8.n, k = 0, strict = defaults$8.strict, anchor = defaults$8.anchor} = {}) { + return window$1({k: n, reduce: (Y) => d3.mean(Y) + k * (d3.deviation(Y) || 0), strict, anchor}); +} + +const defaults$7 = { + ariaLabel: "tick", + fill: null, + stroke: "currentColor" +}; + +class AbstractTick extends Mark { + constructor(data, channels, options) { + super(data, channels, options, defaults$7); + markers(this, options); + } + render(index, scales, channels, dimensions, context) { + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(this._transform, this, scales) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("line") + .call(applyDirectStyles, this) + .attr("x1", this._x1(scales, channels, dimensions)) + .attr("x2", this._x2(scales, channels, dimensions)) + .attr("y1", this._y1(scales, channels, dimensions)) + .attr("y2", this._y2(scales, channels, dimensions)) + .call(applyChannelStyles, this, channels) + .call(applyMarkers, this, channels, context) + ) + .node(); + } +} + +class TickX extends AbstractTick { + constructor(data, options = {}) { + const {x, y, inset = 0, insetTop = inset, insetBottom = inset} = options; + super( + data, + { + x: {value: x, scale: "x"}, + y: {value: y, scale: "y", type: "band", optional: true} + }, + options + ); + this.insetTop = number$1(insetTop); + this.insetBottom = number$1(insetBottom); + } + _transform(selection, mark, {x}) { + selection.call(applyTransform, mark, {x}, offset, 0); + } + _x1(scales, {x: X}) { + return (i) => X[i]; + } + _x2(scales, {x: X}) { + return (i) => X[i]; + } + _y1({y}, {y: Y}, {marginTop}) { + const {insetTop} = this; + return Y && y ? (i) => Y[i] + insetTop : marginTop + insetTop; + } + _y2({y}, {y: Y}, {height, marginBottom}) { + const {insetBottom} = this; + return Y && y ? (i) => Y[i] + y.bandwidth() - insetBottom : height - marginBottom - insetBottom; + } +} + +class TickY extends AbstractTick { + constructor(data, options = {}) { + const {x, y, inset = 0, insetRight = inset, insetLeft = inset} = options; + super( + data, + { + y: {value: y, scale: "y"}, + x: {value: x, scale: "x", type: "band", optional: true} + }, + options + ); + this.insetRight = number$1(insetRight); + this.insetLeft = number$1(insetLeft); + } + _transform(selection, mark, {y}) { + selection.call(applyTransform, mark, {y}, 0, offset); + } + _x1({x}, {x: X}, {marginLeft}) { + const {insetLeft} = this; + return X && x ? (i) => X[i] + insetLeft : marginLeft + insetLeft; + } + _x2({x}, {x: X}, {width, marginRight}) { + const {insetRight} = this; + return X && x ? (i) => X[i] + x.bandwidth() - insetRight : width - marginRight - insetRight; + } + _y1(scales, {y: Y}) { + return (i) => Y[i]; + } + _y2(scales, {y: Y}) { + return (i) => Y[i]; + } +} + +function tickX(data, {x = identity$1, ...options} = {}) { + return new TickX(data, {...options, x}); +} + +function tickY(data, {y = identity$1, ...options} = {}) { + return new TickY(data, {...options, y}); +} + +// Returns a composite mark for producing a horizontal box plot, applying the +// necessary statistical transforms. The boxes are grouped by y, if present. +function boxX( + data, + { + x = identity$1, + y = null, + fill = "#ccc", + fillOpacity, + stroke = "currentColor", + strokeOpacity, + strokeWidth = 2, + sort, + ...options + } = {} +) { + const group = y != null ? groupY : groupZ$1; + return marks( + ruleY(data, group({x1: loqr1, x2: hiqr2}, {x, y, stroke, strokeOpacity, ...options})), + barX(data, group({x1: "p25", x2: "p75"}, {x, y, fill, fillOpacity, ...options})), + tickX(data, group({x: "p50"}, {x, y, stroke, strokeOpacity, strokeWidth, sort, ...options})), + dot(data, map({x: oqr}, {x, y, z: y, stroke, strokeOpacity, ...options})) + ); +} + +// Returns a composite mark for producing a vertical box plot, applying the +// necessary statistical transforms. The boxes are grouped by x, if present. +function boxY( + data, + { + y = identity$1, + x = null, + fill = "#ccc", + fillOpacity, + stroke = "currentColor", + strokeOpacity, + strokeWidth = 2, + sort, + ...options + } = {} +) { + const group = x != null ? groupX : groupZ$1; + return marks( + ruleX(data, group({y1: loqr1, y2: hiqr2}, {x, y, stroke, strokeOpacity, ...options})), + barY(data, group({y1: "p25", y2: "p75"}, {x, y, fill, fillOpacity, ...options})), + tickY(data, group({y: "p50"}, {x, y, stroke, strokeOpacity, strokeWidth, sort, ...options})), + dot(data, map({y: oqr}, {x, y, z: x, stroke, strokeOpacity, ...options})) + ); +} + +// A map function that returns only outliers, returning NaN for non-outliers +function oqr(values) { + const r1 = loqr1(values); + const r2 = hiqr2(values); + return values.map((v) => (v < r1 || v > r2 ? v : NaN)); +} + +function loqr1(values) { + const lo = quartile1(values) * 2.5 - quartile3(values) * 1.5; + return d3.min(values, (d) => (d >= lo ? d : NaN)); +} + +function hiqr2(values) { + const hi = quartile3(values) * 2.5 - quartile1(values) * 1.5; + return d3.max(values, (d) => (d <= hi ? d : NaN)); +} + +function quartile1(values) { + return d3.quantile(values, 0.25); +} + +function quartile3(values) { + return d3.quantile(values, 0.75); +} + +const defaults$6 = { + ariaLabel: "raster", + stroke: null, + pixelSize: 1 +}; + +function number(input, name) { + const x = +input; + if (isNaN(x)) throw new Error(`invalid ${name}: ${input}`); + return x; +} + +function integer(input, name) { + const x = Math.floor(input); + if (isNaN(x)) throw new Error(`invalid ${name}: ${input}`); + return x; +} + +class AbstractRaster extends Mark { + constructor(data, channels, options = {}, defaults) { + let { + width, + height, + x, + y, + x1 = x == null ? 0 : undefined, + y1 = y == null ? 0 : undefined, + x2 = x == null ? width : undefined, + y2 = y == null ? height : undefined, + pixelSize = defaults.pixelSize, + blur = 0, + interpolate + } = options; + if (width != null) width = integer(width, "width"); + if (height != null) height = integer(height, "height"); + // These represent the (minimum) bounds of the raster; they are not + // evaluated for each datum. Also, if x and y are not specified explicitly, + // then these bounds are used to compute the dense linear grid. + if (x1 != null) x1 = number(x1, "x1"); + if (y1 != null) y1 = number(y1, "y1"); + if (x2 != null) x2 = number(x2, "x2"); + if (y2 != null) y2 = number(y2, "y2"); + if (x == null && (x1 == null || x2 == null)) throw new Error("missing x"); + if (y == null && (y1 == null || y2 == null)) throw new Error("missing y"); + if (data != null && width != null && height != null) { + // If x and y are not given, assume the data is a dense array of samples + // covering the entire grid in row-major order. These defaults allow + // further shorthand where x and y represent grid column and row index. + // TODO If we know that the x and y scales are linear, then we could avoid + // materializing these columns to improve performance. + if (x === undefined && x1 != null && x2 != null) x = denseX(x1, x2, width); + if (y === undefined && y1 != null && y2 != null) y = denseY(y1, y2, width, height); + } + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + x1: {value: x1 == null ? null : [x1], scale: "x", optional: true, filter: null}, + y1: {value: y1 == null ? null : [y1], scale: "y", optional: true, filter: null}, + x2: {value: x2 == null ? null : [x2], scale: "x", optional: true, filter: null}, + y2: {value: y2 == null ? null : [y2], scale: "y", optional: true, filter: null}, + ...channels + }, + options, + defaults + ); + this.width = width; + this.height = height; + this.pixelSize = number(pixelSize, "pixelSize"); + this.blur = number(blur, "blur"); + this.interpolate = x == null || y == null ? null : maybeInterpolate(interpolate); // interpolation requires x & y + } +} + +class Raster extends AbstractRaster { + constructor(data, options = {}) { + const {imageRendering} = options; + if (data == null) { + const {fill, fillOpacity} = options; + if (maybeNumberChannel(fillOpacity)[0] !== undefined) options = sampler("fillOpacity", options); + if (maybeColorChannel(fill)[0] !== undefined) options = sampler("fill", options); + } + super(data, undefined, options, defaults$6); + this.imageRendering = impliedString(imageRendering, "auto"); + } + // Ignore the color scale, so the fill channel is returned unscaled. + scale(channels, {color, ...scales}, context) { + return super.scale(channels, scales, context); + } + render(index, scales, values, dimensions, context) { + const color = scales[values.channels.fill?.scale] ?? ((x) => x); + const {x: X, y: Y} = values; + const {document} = context; + const [x1, y1, x2, y2] = renderBounds(values, dimensions, context); + const dx = x2 - x1; + const dy = y2 - y1; + const {pixelSize: k, width: w = Math.round(Math.abs(dx) / k), height: h = Math.round(Math.abs(dy) / k)} = this; + const n = w * h; + + // Interpolate the samples to fill the raster grid. If interpolate is null, + // then a continuous function is being sampled, and the raster grid is + // already aligned with the canvas. + let {fill: F, fillOpacity: FO} = values; + let offset = 0; + if (this.interpolate) { + const kx = w / dx; + const ky = h / dy; + const IX = map$1(X, (x) => (x - x1) * kx, Float64Array); + const IY = map$1(Y, (y) => (y - y1) * ky, Float64Array); + if (F) F = this.interpolate(index, w, h, IX, IY, F); + if (FO) FO = this.interpolate(index, w, h, IX, IY, FO); + } + + // When faceting without interpolation, as when sampling a continuous + // function, offset into the dense grid based on the current facet index. + else if (this.data == null && index) offset = index.fi * n; + + // Render the raster grid to the canvas, blurring if needed. + const canvas = document.createElement("canvas"); + canvas.width = w; + canvas.height = h; + const context2d = canvas.getContext("2d"); + const image = context2d.createImageData(w, h); + const imageData = image.data; + let {r, g, b} = d3.rgb(this.fill) ?? {r: 0, g: 0, b: 0}; + let a = (this.fillOpacity ?? 1) * 255; + for (let i = 0; i < n; ++i) { + const j = i << 2; + if (F) { + const fi = color(F[i + offset]); + if (fi == null) { + imageData[j + 3] = 0; + continue; + } + ({r, g, b} = d3.rgb(fi)); + } + if (FO) a = FO[i + offset] * 255; + imageData[j + 0] = r; + imageData[j + 1] = g; + imageData[j + 2] = b; + imageData[j + 3] = a; + } + if (this.blur > 0) d3.blurImage(image, this.blur); + context2d.putImageData(image, 0, 0); + + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, scales) + .call((g) => + g + .append("image") + .attr("transform", `translate(${x1},${y1}) scale(${Math.sign(x2 - x1)},${Math.sign(y2 - y1)})`) + .attr("width", Math.abs(dx)) + .attr("height", Math.abs(dy)) + .attr("preserveAspectRatio", "none") + .call(applyAttr, "image-rendering", this.imageRendering) + .call(applyDirectStyles, this) + .attr("xlink:href", canvas.toDataURL()) + ) + .node(); + } +} + +function maybeTuples(k, data, options) { + if (arguments.length < 3) (options = data), (data = null); + let {x, y, [k]: z, ...rest} = options; + // Because we use implicit x and y when z is a function of (x, y), and when + // data is a dense grid, we must further disambiguate by testing whether data + // contains [x, y, z?] tuples. Hence you can’t use this shorthand with a + // transform that lazily generates tuples, but that seems reasonable since + // this is just for convenience anyway. + if (x === undefined && y === undefined && isTuples(data)) { + (x = first), (y = second); + if (z === undefined) z = third; + } + return [data, {...rest, x, y, [k]: z}]; +} + +function raster() { + const [data, options] = maybeTuples("fill", ...arguments); + return new Raster( + data, + data == null || options.fill !== undefined || options.fillOpacity !== undefined + ? options + : {...options, fill: identity$1} + ); +} + +// See rasterBounds; this version is called during render. +function renderBounds({x1, y1, x2, y2}, dimensions, {projection}) { + const {width, height, marginTop, marginRight, marginBottom, marginLeft} = dimensions; + return [ + x1 && projection == null ? x1[0] : marginLeft, + y1 && projection == null ? y1[0] : marginTop, + x2 && projection == null ? x2[0] : width - marginRight, + y2 && projection == null ? y2[0] : height - marginBottom + ]; +} + +// If x1, y1, x2, y2 were specified, and no projection is in use (and thus the +// raster grid is necessarily an axis-aligned rectangle), then we can compute +// tighter bounds for the image, improving resolution. +function rasterBounds({x1, y1, x2, y2}, scales, dimensions, context) { + const channels = {}; + if (x1) channels.x1 = x1; + if (y1) channels.y1 = y1; + if (x2) channels.x2 = x2; + if (y2) channels.y2 = y2; + return renderBounds(valueObject(channels, scales), dimensions, context); +} + +// Evaluates the function with the given name, if it exists, on the raster grid, +// generating a channel of the same name. +function sampler(name, options = {}) { + const {[name]: value} = options; + if (typeof value !== "function") throw new Error(`invalid ${name}: not a function`); + return initializer({...options, [name]: undefined}, function (data, facets, channels, scales, dimensions, context) { + const {x, y} = scales; + // TODO Allow projections, if invertible. + if (!x) throw new Error("missing scale: x"); + if (!y) throw new Error("missing scale: y"); + const [x1, y1, x2, y2] = rasterBounds(channels, scales, dimensions, context); + const dx = x2 - x1; + const dy = y2 - y1; + const {pixelSize: k} = this; + // Note: this must exactly match the defaults in render above! + const {width: w = Math.round(Math.abs(dx) / k), height: h = Math.round(Math.abs(dy) / k)} = options; + // TODO Hint to use a typed array when possible? + const V = new Array(w * h * (facets ? facets.length : 1)); + const kx = dx / w; + const ky = dy / h; + let i = 0; + for (const facet of facets ?? [undefined]) { + for (let yi = 0.5; yi < h; ++yi) { + for (let xi = 0.5; xi < w; ++xi, ++i) { + V[i] = value(x.invert(x1 + xi * kx), y.invert(y1 + yi * ky), facet); + } + } + } + return {data: V, facets, channels: {[name]: {value: V, scale: "auto"}}}; + }); +} + +function maybeInterpolate(interpolate) { + if (typeof interpolate === "function") return interpolate; + if (interpolate == null) return interpolateNone; + switch (`${interpolate}`.toLowerCase()) { + case "none": + return interpolateNone; + case "nearest": + return interpolateNearest; + case "barycentric": + return interpolatorBarycentric(); + case "random-walk": + return interpolatorRandomWalk(); + } + throw new Error(`invalid interpolate: ${interpolate}`); +} + +// Applies a simple forward mapping of samples, binning them into pixels without +// any blending or interpolation. Note: if multiple samples map to the same +// pixel, the last one wins; this can introduce bias if the points are not in +// random order, so use Plot.shuffle to randomize the input if needed. +function interpolateNone(index, width, height, X, Y, V) { + const W = new Array(width * height); + for (const i of index) { + if (X[i] < 0 || X[i] >= width || Y[i] < 0 || Y[i] >= height) continue; + W[Math.floor(Y[i]) * width + Math.floor(X[i])] = V[i]; + } + return W; +} + +function interpolatorBarycentric({random = d3.randomLcg(42)} = {}) { + return (index, width, height, X, Y, V) => { + // Interpolate the interior of all triangles with barycentric coordinates + const {points, triangles, hull} = d3.Delaunay.from( + index, + (i) => X[i], + (i) => Y[i] + ); + const W = new V.constructor(width * height).fill(NaN); + const S = new Uint8Array(width * height); // 1 if pixel has been seen. + const mix = mixer(V, random); + + for (let i = 0; i < triangles.length; i += 3) { + const ta = triangles[i]; + const tb = triangles[i + 1]; + const tc = triangles[i + 2]; + const Ax = points[2 * ta]; + const Bx = points[2 * tb]; + const Cx = points[2 * tc]; + const Ay = points[2 * ta + 1]; + const By = points[2 * tb + 1]; + const Cy = points[2 * tc + 1]; + const x1 = Math.min(Ax, Bx, Cx); + const x2 = Math.max(Ax, Bx, Cx); + const y1 = Math.min(Ay, By, Cy); + const y2 = Math.max(Ay, By, Cy); + const z = (By - Cy) * (Ax - Cx) + (Ay - Cy) * (Cx - Bx); + if (!z) continue; + const va = V[index[ta]]; + const vb = V[index[tb]]; + const vc = V[index[tc]]; + for (let x = Math.floor(x1); x < x2; ++x) { + for (let y = Math.floor(y1); y < y2; ++y) { + if (x < 0 || x >= width || y < 0 || y >= height) continue; + const xp = x + 0.5; // sample pixel centroids + const yp = y + 0.5; + const ga = ((By - Cy) * (xp - Cx) + (yp - Cy) * (Cx - Bx)) / z; + if (ga < 0) continue; + const gb = ((Cy - Ay) * (xp - Cx) + (yp - Cy) * (Ax - Cx)) / z; + if (gb < 0) continue; + const gc = 1 - ga - gb; + if (gc < 0) continue; + const i = x + width * y; + W[i] = mix(va, ga, vb, gb, vc, gc, x, y); + S[i] = 1; + } + } + } + extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix); + return W; + }; +} + +// Extrapolate by finding the closest point on the hull. +function extrapolateBarycentric(W, S, X, Y, V, width, height, hull, index, mix) { + X = Float64Array.from(hull, (i) => X[index[i]]); + Y = Float64Array.from(hull, (i) => Y[index[i]]); + V = Array.from(hull, (i) => V[index[i]]); + const n = X.length; + const rays = Array.from({length: n}, (_, j) => ray(j, X, Y)); + let k = 0; + for (let y = 0; y < height; ++y) { + const yp = y + 0.5; + for (let x = 0; x < width; ++x) { + const i = x + width * y; + if (!S[i]) { + const xp = x + 0.5; + for (let l = 0; l < n; ++l) { + const j = (n + k + (l % 2 ? (l + 1) / 2 : -l / 2)) % n; + if (rays[j](xp, yp)) { + const t = segmentProject(X.at(j - 1), Y.at(j - 1), X[j], Y[j], xp, yp); + W[i] = mix(V.at(j - 1), t, V[j], 1 - t, V[j], 0, x, y); + k = j; + break; + } + } + } + } + } +} + +// Projects a point p = [x, y] onto the line segment [p1, p2], returning the +// projected coordinates p’ as t in [0, 1] with p’ = t p1 + (1 - t) p2. +function segmentProject(x1, y1, x2, y2, x, y) { + const dx = x2 - x1; + const dy = y2 - y1; + const a = dx * (x2 - x) + dy * (y2 - y); + const b = dx * (x - x1) + dy * (y - y1); + return a > 0 && b > 0 ? a / (a + b) : +(a > b); +} + +function cross(xa, ya, xb, yb) { + return xa * yb - xb * ya; +} + +function ray(j, X, Y) { + const n = X.length; + const xc = X.at(j - 2); + const yc = Y.at(j - 2); + const xa = X.at(j - 1); + const ya = Y.at(j - 1); + const xb = X[j]; + const yb = Y[j]; + const xd = X.at(j + 1 - n); + const yd = Y.at(j + 1 - n); + const dxab = xa - xb; + const dyab = ya - yb; + const dxca = xc - xa; + const dyca = yc - ya; + const dxbd = xb - xd; + const dybd = yb - yd; + const hab = Math.hypot(dxab, dyab); + const hca = Math.hypot(dxca, dyca); + const hbd = Math.hypot(dxbd, dybd); + return (x, y) => { + const dxa = x - xa; + const dya = y - ya; + const dxb = x - xb; + const dyb = y - yb; + return ( + cross(dxa, dya, dxb, dyb) > -1e-6 && + cross(dxa, dya, dxab, dyab) * hca - cross(dxa, dya, dxca, dyca) * hab > -1e-6 && + cross(dxb, dyb, dxbd, dybd) * hab - cross(dxb, dyb, dxab, dyab) * hbd <= 0 + ); + }; +} + +function interpolateNearest(index, width, height, X, Y, V) { + const W = new V.constructor(width * height); + const delaunay = d3.Delaunay.from( + index, + (i) => X[i], + (i) => Y[i] + ); + // memoization of delaunay.find for the line start (iy) and pixel (ix) + let iy, ix; + for (let y = 0.5, k = 0; y < height; ++y) { + ix = iy; + for (let x = 0.5; x < width; ++x, ++k) { + ix = delaunay.find(x, y, ix); + if (x === 0.5) iy = ix; + W[k] = V[index[ix]]; + } + } + return W; +} + +// https://observablehq.com/@observablehq/walk-on-spheres-precision +function interpolatorRandomWalk({random = d3.randomLcg(42), minDistance = 0.5, maxSteps = 2} = {}) { + return (index, width, height, X, Y, V) => { + const W = new V.constructor(width * height); + const delaunay = d3.Delaunay.from( + index, + (i) => X[i], + (i) => Y[i] + ); + // memoization of delaunay.find for the line start (iy), pixel (ix), and wos step (iw) + let iy, ix, iw; + for (let y = 0.5, k = 0; y < height; ++y) { + ix = iy; + for (let x = 0.5; x < width; ++x, ++k) { + let cx = x; + let cy = y; + iw = ix = delaunay.find(cx, cy, ix); + if (x === 0.5) iy = ix; + let distance; // distance to closest sample + let step = 0; // count of steps for this walk + while ((distance = Math.hypot(X[index[iw]] - cx, Y[index[iw]] - cy)) > minDistance && step < maxSteps) { + const angle = random(x, y, step) * 2 * Math.PI; + cx += Math.cos(angle) * distance; + cy += Math.sin(angle) * distance; + iw = delaunay.find(cx, cy, iw); + ++step; + } + W[k] = V[index[iw]]; + } + } + return W; + }; +} + +function blend(a, ca, b, cb, c, cc) { + return ca * a + cb * b + cc * c; +} + +function pick(random) { + return (a, ca, b, cb, c, cc, x, y) => { + const u = random(x, y); + return u < ca ? a : u < ca + cb ? b : c; + }; +} + +function mixer(F, random) { + return isNumeric(F) || isTemporal(F) ? blend : pick(random); +} + +function denseX(x1, x2, width) { + return { + transform(data) { + const n = data.length; + const X = new Float64Array(n); + const kx = (x2 - x1) / width; + const x0 = x1 + kx / 2; + for (let i = 0; i < n; ++i) X[i] = (i % width) * kx + x0; + return X; + } + }; +} + +function denseY(y1, y2, width, height) { + return { + transform(data) { + const n = data.length; + const Y = new Float64Array(n); + const ky = (y2 - y1) / height; + const y0 = y1 + ky / 2; + for (let i = 0; i < n; ++i) Y[i] = (Math.floor(i / width) % height) * ky + y0; + return Y; + } + }; +} + +const defaults$5 = { + ariaLabel: "contour", + fill: "none", + stroke: "currentColor", + strokeMiterlimit: 1, + pixelSize: 2 +}; + +class Contour extends AbstractRaster { + constructor(data, {smooth = true, value, ...options} = {}) { + const channels = styles({}, options, defaults$5); + + // If value is not specified explicitly, look for a channel to promote. If + // more than one channel is present, throw an error. (To disambiguate, + // specify the value option explicitly.) + if (value === undefined) { + for (const key in channels) { + if (channels[key].value != null) { + if (value !== undefined) throw new Error("ambiguous contour value"); + value = options[key]; + options[key] = "value"; + } + } + } + + // For any channel specified as the literal (contour threshold) "value" + // (maybe because of the promotion above), propagate the label from the + // original value definition. + if (value != null) { + const v = {transform: (D) => D.map((d) => d.value), label: labelof(value)}; + for (const key in channels) { + if (options[key] === "value") { + options[key] = v; + } + } + } + + // If the data is null, then we’ll construct the raster grid by evaluating a + // function for each point in a dense grid. The value channel is populated + // by the sampler initializer, and hence is not passed to super to avoid + // computing it before there’s data. + if (data == null) { + if (value == null) throw new Error("missing contour value"); + options = sampler("value", {value, ...options}); + value = null; + } + + // Otherwise if data was provided, it represents a discrete set of spatial + // samples (often a grid, but not necessarily). If no interpolation method + // was specified, default to nearest. + else { + let {interpolate} = options; + if (value === undefined) value = identity$1; + if (interpolate === undefined) options.interpolate = "nearest"; + } + + // Wrap the options in our initializer that computes the contour geometries; + // this runs after any other initializers (and transforms). + super(data, {value: {value, optional: true}}, contourGeometry(options), defaults$5); + + // With the exception of the x, y, x1, y1, x2, y2, and value channels, this + // mark’s channels are not evaluated on the initial data but rather on the + // contour multipolygons generated in the initializer. + const contourChannels = {geometry: {value: identity$1}}; + for (const key in this.channels) { + const channel = this.channels[key]; + const {scale} = channel; + if (scale === "x" || scale === "y" || key === "value") continue; + contourChannels[key] = channel; + delete this.channels[key]; + } + this.contourChannels = contourChannels; + this.smooth = !!smooth; + } + filter(index, {x, y, value, ...channels}, values) { + // Only filter channels constructed by the contourGeometry initializer; the + // x, y, and value channels must be filtered by the initializer itself. + return super.filter(index, channels, values); + } + render(index, scales, channels, dimensions, context) { + const {geometry: G} = channels; + const path = d3.geoPath(); + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, scales) + .call((g) => { + g.selectAll() + .data(index) + .enter() + .append("path") + .call(applyDirectStyles, this) + .attr("d", (i) => path(G[i])) + .call(applyChannelStyles, this, channels); + }) + .node(); + } +} + +function contourGeometry({thresholds, interval, ...options}) { + thresholds = maybeThresholds(thresholds, interval, d3.thresholdSturges); + return initializer(options, function (data, facets, channels, scales, dimensions, context) { + const [x1, y1, x2, y2] = rasterBounds(channels, scales, dimensions, context); + const dx = x2 - x1; + const dy = y2 - y1; + const {pixelSize: k, width: w = Math.round(Math.abs(dx) / k), height: h = Math.round(Math.abs(dy) / k)} = this; + const kx = w / dx; + const ky = h / dy; + const V = channels.value.value; + const VV = []; // V per facet + + // Interpolate the raster grid, as needed. + if (this.interpolate) { + const {x: X, y: Y} = applyPosition(channels, scales, context); + // Convert scaled (screen) coordinates to grid (canvas) coordinates. + const IX = map$1(X, (x) => (x - x1) * kx, Float64Array); + const IY = map$1(Y, (y) => (y - y1) * ky, Float64Array); + // The contour mark normally skips filtering on x, y, and value, so here + // we’re careful to use different names (0, 1, 2) when filtering. + const ichannels = [channels.x, channels.y, channels.value]; + const ivalues = [IX, IY, V]; + for (const facet of facets) { + const index = this.filter(facet, ichannels, ivalues); + VV.push(this.interpolate(index, w, h, IX, IY, V)); + } + } + + // Otherwise, chop up the existing dense raster grid into facets, if needed. + // V must be a dense grid in projected coordinates; if there are multiple + // facets, then V must be laid out vertically as facet 0, 1, 2… etc. + else if (facets) { + const n = w * h; + const m = facets.length; + for (let i = 0; i < m; ++i) VV.push(V.slice(i * n, i * n + n)); + } else { + VV.push(V); + } + + // Blur the raster grid, if desired. + if (this.blur > 0) for (const V of VV) d3.blur2({data: V, width: w, height: h}, this.blur); + + // Compute the contour thresholds. + const T = maybeTicks(thresholds, V, ...finiteExtent(VV)); + if (T === null) throw new Error(`unsupported thresholds: ${thresholds}`); + + // Compute the (maybe faceted) contours. + const {contour} = d3.contours().size([w, h]).smooth(this.smooth); + const contourData = []; + const contourFacets = []; + for (const V of VV) { + contourFacets.push(d3.range(contourData.length, contourData.push(...map$1(T, (t) => contour(V, t))))); + } + + // Rescale the contour multipolygon from grid to screen coordinates. + for (const {coordinates} of contourData) { + for (const rings of coordinates) { + for (const ring of rings) { + for (const point of ring) { + point[0] = point[0] / kx + x1; + point[1] = point[1] / ky + y1; + } + } + } + } + + // Compute the deferred channels. + return { + data: contourData, + facets: contourFacets, + channels: createChannels(this.contourChannels, contourData) + }; + }); +} + +// Apply the thresholds interval, function, or count, and return an array of +// ticks. d3-contour unlike d3-array doesn’t pass the min and max automatically, +// so we do that here to normalize, and also so we can share consistent +// thresholds across facets. When an interval is used, note that the lowest +// threshold should be below (or equal) to the lowest value, or else some data +// will be missing. +function maybeTicks(thresholds, V, min, max) { + if (typeof thresholds?.range === "function") return thresholds.range(thresholds.floor(min), max); + if (typeof thresholds === "function") thresholds = thresholds(V, min, max); + if (typeof thresholds !== "number") return arrayify(thresholds); + const tz = d3.ticks(...d3.nice(min, max, thresholds), thresholds); + while (tz[tz.length - 1] >= max) tz.pop(); + while (tz[1] < min) tz.shift(); + return tz; +} + +function contour() { + return new Contour(...maybeTuples("value", ...arguments)); +} + +function finiteExtent(VV) { + return [d3.min(VV, (V) => d3.min(V, finite)), d3.max(VV, (V) => d3.max(V, finite))]; +} + +function finite(x) { + return isFinite(x) ? x : NaN; +} + +function crosshair(data, options) { + return crosshairK(pointer, data, options); +} + +function crosshairX(data, options = {}) { + return crosshairK(pointerX, data, options); +} + +function crosshairY(data, options = {}) { + return crosshairK(pointerY, data, options); +} + +function crosshairK(pointer, data, options = {}) { + const {x, y, maxRadius} = options; + const p = pointer({px: x, py: y, maxRadius}); + const M = []; + if (x != null) M.push(ruleX(data, ruleOptions("x", {...p, inset: -6}, options))); + if (y != null) M.push(ruleY(data, ruleOptions("y", {...p, inset: -6}, options))); + if (x != null) M.push(text(data, textOptions("x", {...p, dy: 9, frameAnchor: "bottom", lineAnchor: "top"}, options))); + if (y != null) M.push(text(data, textOptions("y", {...p, dx: -9, frameAnchor: "left", textAnchor: "end"}, options))); + for (const m of M) m.ariaLabel = `crosshair ${m.ariaLabel}`; + return marks(...M); +} + +function markOptions( + k, + {channels: pointerChannels, ...pointerOptions}, + {facet, facetAnchor, fx, fy, [k]: p, channels, transform, initializer} +) { + return { + ...pointerOptions, + facet, + facetAnchor, + fx, + fy, + [k]: p, + channels: {...pointerChannels, ...channels}, + transform, + initializer: pxpy(k, initializer) + }; +} + +// Wrap the initializer, if any, mapping px and py to x and y temporarily (e.g., +// for hexbin) then mapping back to px and py for rendering. +function pxpy(k, i) { + if (i == null) return i; + return function (data, facets, {x: x1, y: y1, px, py, ...c1}, ...args) { + const {channels: {x, y, ...c} = {}, ...rest} = i.call(this, data, facets, {...c1, x: px, y: py}, ...args); + return { + channels: { + ...c, + ...(x && {px: x, ...(k === "x" && {x})}), + ...(y && {py: y, ...(k === "y" && {y})}) + }, + ...rest + }; + }; +} + +function ruleOptions(k, pointerOptions, options) { + const { + color = "currentColor", + opacity = 0.2, + ruleStroke: stroke = color, + ruleStrokeOpacity: strokeOpacity = opacity, + ruleStrokeWidth: strokeWidth + } = options; + return { + ...markOptions(k, pointerOptions, options), + stroke, + strokeOpacity, + strokeWidth + }; +} + +function textOptions(k, pointerOptions, options) { + const { + color = "currentColor", + textFill: fill = color, + textFillOpacity: fillOpacity, + textStroke: stroke = "var(--plot-background)", + textStrokeOpacity: strokeOpacity, + textStrokeWidth: strokeWidth = 5 + } = options; + return { + ...markOptions(k, pointerOptions, textChannel(k, options)), + fill, + fillOpacity, + stroke, + strokeOpacity, + strokeWidth + }; +} + +// Rather than aliasing text to have the same definition as x and y, we use an +// initializer to alias the channel values, such that the text channel can be +// derived by an initializer such as hexbin. +function textChannel(source, options) { + return initializer(options, (data, facets, channels) => { + return {channels: {text: {value: getSource(channels, source)?.value}}}; + }); +} + +const delaunayLinkDefaults = { + ariaLabel: "delaunay link", + fill: "none", + stroke: "currentColor", + strokeMiterlimit: 1 +}; + +const delaunayMeshDefaults = { + ariaLabel: "delaunay mesh", + fill: null, + stroke: "currentColor", + strokeOpacity: 0.2 +}; + +const hullDefaults = { + ariaLabel: "hull", + fill: "none", + stroke: "currentColor", + strokeWidth: 1.5, + strokeMiterlimit: 1 +}; + +const voronoiDefaults = { + ariaLabel: "voronoi", + fill: "none", + stroke: "currentColor", + strokeMiterlimit: 1 +}; + +const voronoiMeshDefaults = { + ariaLabel: "voronoi mesh", + fill: null, + stroke: "currentColor", + strokeOpacity: 0.2 +}; + +class DelaunayLink extends Mark { + constructor(data, options = {}) { + const {x, y, z, curve, tension} = options; + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + z: {value: z, optional: true} + }, + options, + delaunayLinkDefaults + ); + this.curve = maybeCurve(curve, tension); + markers(this, options); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x: X, y: Y, z: Z} = channels; + const {curve} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const xi = X ? (i) => X[i] : constant(cx); + const yi = Y ? (i) => Y[i] : constant(cy); + const mark = this; + + function links(index) { + let i = -1; + const newIndex = []; + const newChannels = {}; + for (const k in channels) newChannels[k] = []; + const X1 = []; + const X2 = []; + const Y1 = []; + const Y2 = []; + + function link(ti, tj) { + ti = index[ti]; + tj = index[tj]; + newIndex.push(++i); + X1[i] = xi(ti); + Y1[i] = yi(ti); + X2[i] = xi(tj); + Y2[i] = yi(tj); + for (const k in channels) newChannels[k].push(channels[k][tj]); + } + + const {halfedges, hull, triangles} = d3.Delaunay.from(index, xi, yi); + for (let i = 0; i < halfedges.length; ++i) { + // inner edges + const j = halfedges[i]; + if (j > i) link(triangles[i], triangles[j]); + } + for (let i = 0; i < hull.length; ++i) { + // convex hull + link(hull[i], hull[(i + 1) % hull.length]); + } + + d3.select(this) + .selectAll() + .data(newIndex) + .enter() + .append("path") + .call(applyDirectStyles, mark) + .attr("d", (i) => { + const p = d3.pathRound(); + const c = curve(p); + c.lineStart(); + c.point(X1[i], Y1[i]); + c.point(X2[i], Y2[i]); + c.lineEnd(); + return p; + }) + .call(applyChannelStyles, mark, newChannels) + .call(applyMarkers, mark, newChannels, context); + } + + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {x: X && x, y: Y && y}) + .call( + Z + ? (g) => + g + .selectAll() + .data(d3.group(index, (i) => Z[i]).values()) + .enter() + .append("g") + .each(links) + : (g) => g.datum(index).each(links) + ) + .node(); + } +} + +class AbstractDelaunayMark extends Mark { + constructor(data, options = {}, defaults, zof = ({z}) => z) { + const {x, y} = options; + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + z: {value: zof(options), optional: true} + }, + options, + defaults + ); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x: X, y: Y, z: Z} = channels; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const xi = X ? (i) => X[i] : constant(cx); + const yi = Y ? (i) => Y[i] : constant(cy); + const mark = this; + + function mesh(index) { + const delaunay = d3.Delaunay.from(index, xi, yi); + d3.select(this) + .append("path") + .datum(index[0]) + .call(applyDirectStyles, mark) + .attr("d", mark._render(delaunay, dimensions)) + .call(applyChannelStyles, mark, channels); + } + + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {x: X && x, y: Y && y}) + .call( + Z + ? (g) => + g + .selectAll() + .data(d3.group(index, (i) => Z[i]).values()) + .enter() + .append("g") + .each(mesh) + : (g) => g.datum(index).each(mesh) + ) + .node(); + } +} + +class DelaunayMesh extends AbstractDelaunayMark { + constructor(data, options = {}) { + super(data, options, delaunayMeshDefaults); + this.fill = "none"; + } + _render(delaunay) { + return delaunay.render(); + } +} + +class Hull extends AbstractDelaunayMark { + constructor(data, options = {}) { + super(data, options, hullDefaults, maybeZ); + } + _render(delaunay) { + return delaunay.renderHull(); + } +} + +class Voronoi extends Mark { + constructor(data, options = {}) { + const {x, y, z} = options; + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + z: {value: z, optional: true} + }, + options, + voronoiDefaults + ); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x: X, y: Y, z: Z} = channels; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const xi = X ? (i) => X[i] : constant(cx); + const yi = Y ? (i) => Y[i] : constant(cy); + const mark = this; + + function cells(index) { + const delaunay = d3.Delaunay.from(index, xi, yi); + const voronoi = voronoiof(delaunay, dimensions); + d3.select(this) + .selectAll() + .data(index) + .enter() + .append("path") + .call(applyDirectStyles, mark) + .attr("d", (_, i) => voronoi.renderCell(i)) + .call(applyChannelStyles, mark, channels); + } + + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {x: X && x, y: Y && y}) + .call( + Z + ? (g) => + g + .selectAll() + .data(d3.group(index, (i) => Z[i]).values()) + .enter() + .append("g") + .each(cells) + : (g) => g.datum(index).each(cells) + ) + .node(); + } +} + +class VoronoiMesh extends AbstractDelaunayMark { + constructor(data, options) { + super(data, options, voronoiMeshDefaults); + this.fill = "none"; + } + _render(delaunay, dimensions) { + return voronoiof(delaunay, dimensions).render(); + } +} + +function voronoiof(delaunay, dimensions) { + const {width, height, marginTop, marginRight, marginBottom, marginLeft} = dimensions; + return delaunay.voronoi([marginLeft, marginTop, width - marginRight, height - marginBottom]); +} + +function delaunayMark(DelaunayMark, data, {x, y, ...options} = {}) { + [x, y] = maybeTuple(x, y); + return new DelaunayMark(data, {...options, x, y}); +} + +function delaunayLink(data, options) { + return delaunayMark(DelaunayLink, data, options); +} + +function delaunayMesh(data, options) { + return delaunayMark(DelaunayMesh, data, options); +} + +function hull(data, options) { + return delaunayMark(Hull, data, options); +} + +function voronoi(data, options) { + return delaunayMark(Voronoi, data, options); +} + +function voronoiMesh(data, options) { + return delaunayMark(VoronoiMesh, data, options); +} + +const defaults$4 = { + ariaLabel: "density", + fill: "none", + stroke: "currentColor", + strokeMiterlimit: 1 +}; + +class Density extends Mark { + constructor(data, {x, y, z, weight, fill, stroke, ...options} = {}) { + // If fill or stroke is specified as “density”, then temporarily treat these + // as a literal color when computing defaults and maybeZ; below, we’ll unset + // these constant colors back to undefined since they will instead be + // populated by a channel generated by the initializer. + const fillDensity = isDensity(fill) && ((fill = "currentColor"), true); + const strokeDensity = isDensity(stroke) && ((stroke = "currentColor"), true); + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + z: {value: maybeZ({z, fill, stroke}), optional: true}, + weight: {value: weight, optional: true} + }, + densityInitializer({...options, fill, stroke}, fillDensity, strokeDensity), + defaults$4 + ); + if (fillDensity) this.fill = undefined; + if (strokeDensity) this.stroke = undefined; + this.z = z; + } + filter(index) { + return index; // don’t filter contours constructed by initializer + } + render(index, scales, channels, dimensions, context) { + const {contours} = channels; + const path = d3.geoPath(); + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {}) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("path") + .call(applyDirectStyles, this) + .call(applyChannelStyles, this, channels) + .attr("d", (i) => path(contours[i])) + ) + .node(); + } +} + +function density(data, {x, y, ...options} = {}) { + [x, y] = maybeTuple(x, y); + return new Density(data, {...options, x, y}); +} + +const dropChannels = new Set(["x", "y", "z", "weight"]); + +function densityInitializer(options, fillDensity, strokeDensity) { + const k = 100; // arbitrary scale factor for readability + let {bandwidth, thresholds} = options; + bandwidth = bandwidth === undefined ? 20 : +bandwidth; + thresholds = + thresholds === undefined + ? 20 + : typeof thresholds?.[Symbol.iterator] === "function" + ? coerceNumbers(thresholds) + : +thresholds; + return initializer(options, function (data, facets, channels, scales, dimensions, context) { + const W = channels.weight ? coerceNumbers(channels.weight.value) : null; + const Z = channels.z?.value; + const {z} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + const {width, height} = dimensions; + + // Get the (either scaled or projected) xy channels. + const {x: X, y: Y} = applyPosition(channels, scales, context); + + // Group any of the input channels according to the first index associated + // with each z-series or facet. Drop any channels not be needed for + // rendering after the contours are computed. + const newChannels = Object.fromEntries( + Object.entries(channels) + .filter(([key]) => !dropChannels.has(key)) + .map(([key, channel]) => [key, {...channel, value: []}]) + ); + + // If the fill or stroke encodes density, construct new output channels. + const FD = fillDensity && []; + const SD = strokeDensity && []; + + const density = d3.contourDensity() + .x(X ? (i) => X[i] : cx) + .y(Y ? (i) => Y[i] : cy) + .weight(W ? (i) => W[i] : 1) + .size([width, height]) + .bandwidth(bandwidth); + + // Compute the grid for each facet-series. + const facetsContours = []; + for (const facet of facets) { + const facetContours = []; + facetsContours.push(facetContours); + for (const index of Z ? groupZ(facet, Z, z) : [facet]) { + const contour = density.contours(index); + facetContours.push([index, contour]); + } + } + + // If explicit thresholds were not specified, find the maximum density of + // all grids and use this to compute thresholds. + let T = thresholds; + if (!(T instanceof TypedArray)) { + let maxValue = 0; + for (const facetContours of facetsContours) { + for (const [, contour] of facetContours) { + const max = contour.max; + if (max > maxValue) maxValue = max; + } + } + T = Float64Array.from({length: thresholds - 1}, (_, i) => (maxValue * k * (i + 1)) / thresholds); + } + + // Generate contours for each facet-series. + const newFacets = []; + const contours = []; + for (const facetContours of facetsContours) { + const newFacet = []; + newFacets.push(newFacet); + for (const [index, contour] of facetContours) { + for (const t of T) { + newFacet.push(contours.length); + contours.push(contour(t / k)); + if (FD) FD.push(t); + if (SD) SD.push(t); + for (const key in newChannels) { + newChannels[key].value.push(channels[key].value[index[0]]); + } + } + } + } + + // If the fill or stroke encodes density, ensure that a zero value is + // included so that the default color scale domain starts at zero. Otherwise + // if the starting range value is the same as the background color, the + // first contour might not be visible. + if (FD) FD.push(0); + if (SD) SD.push(0); + + return { + data, + facets: newFacets, + channels: { + ...newChannels, + ...(FD && {fill: {value: FD, scale: "color"}}), + ...(SD && {stroke: {value: SD, scale: "color"}}), + contours: {value: contours} + } + }; + }); +} + +function isDensity(value) { + return /^density$/i.test(value); +} + +function differenceY( + data, + { + x1, + x2, + y1, + y2, + x = x1 === undefined && x2 === undefined ? indexOf : undefined, + y = y1 === undefined && y2 === undefined ? identity$1 : undefined, + fill, // ignored + positiveFill = "#3ca951", + negativeFill = "#4269d0", + fillOpacity = 1, + positiveFillOpacity = fillOpacity, + negativeFillOpacity = fillOpacity, + stroke, + strokeOpacity, + z = maybeColorChannel(stroke)[0], + clip, // optional additional clip for area + tip, + render, + ...options + } = {} +) { + [x1, x2] = memoTuple(x, x1, x2); + [y1, y2] = memoTuple(y, y1, y2); + if (x1 === x2 && y1 === y2) y1 = memo(0); + ({tip} = withTip({tip}, "x")); + return marks( + !isNoneish(positiveFill) + ? Object.assign( + area(data, { + x1, + x2, + y1, + y2, + z, + fill: positiveFill, + fillOpacity: positiveFillOpacity, + render: composeRender(render, clipDifferenceY(true)), + clip, + ...options + }), + {ariaLabel: "positive difference"} + ) + : null, + !isNoneish(negativeFill) + ? Object.assign( + area(data, { + x1, + x2, + y1, + y2, + z, + fill: negativeFill, + fillOpacity: negativeFillOpacity, + render: composeRender(render, clipDifferenceY(false)), + clip, + ...options + }), + {ariaLabel: "negative difference"} + ) + : null, + line(data, { + x: x2, + y: y2, + z, + stroke, + strokeOpacity, + tip, + clip: true, + ...options + }) + ); +} + +function memoTuple(x, x1, x2) { + if (x1 === undefined && x2 === undefined) { + // {x} → [x, x] + x1 = x2 = memo(x); + } else if (x1 === undefined) { + // {x2} → [x2, x2] + // {x, x2} → [x, x2] + x2 = memo(x2); + x1 = x === undefined ? x2 : memo(x); + } else if (x2 === undefined) { + // {x1} → [x1, x1] + // {x, x1} → [x1, x] + x1 = memo(x1); + x2 = x === undefined ? x1 : memo(x); + } else { + // {x1, x2} → [x1, x2] + x1 = memo(x1); + x2 = memo(x2); + } + return [x1, x2]; +} + +function memo(v) { + let V; + const {value, label = labelof(value)} = maybeValue(v); + return {transform: (data) => V || (V = valueof(data, value)), label}; +} + +function clipDifferenceY(positive) { + return (index, scales, channels, dimensions, context, next) => { + const {x1, x2} = channels; + const {height} = dimensions; + const y1 = new Float32Array(x1.length); + const y2 = new Float32Array(x2.length); + (positive === inferScaleOrder(scales.y) < 0 ? y1 : y2).fill(height); + const oc = next(index, scales, {...channels, x2: x1, y2}, dimensions, context); + const og = next(index, scales, {...channels, x1: x2, y1}, dimensions, context); + const c = oc.querySelector("g") ?? oc; // applyClip + const g = og.querySelector("g") ?? og; // applyClip + for (let i = 0; c.firstChild; i += 2) { + const id = getClipId(); + const clipPath = create("svg:clipPath", context).attr("id", id).node(); + clipPath.appendChild(c.firstChild); + g.childNodes[i].setAttribute("clip-path", `url(#${id})`); + g.insertBefore(clipPath, g.childNodes[i]); + } + return og; + }; +} + +const defaults$3 = { + ariaLabel: "geo", + fill: "none", + stroke: "currentColor", + strokeWidth: 1, + strokeLinecap: "round", + strokeLinejoin: "round", + strokeMiterlimit: 1 +}; + +class Geo extends Mark { + constructor(data, options = {}) { + const [vr, cr] = maybeNumberChannel(options.r, 3); + super( + data, + { + geometry: {value: options.geometry, scale: "projection"}, + r: {value: vr, scale: "r", filter: positive, optional: true} + }, + withDefaultSort(options), + defaults$3 + ); + this.r = cr; + } + render(index, scales, channels, dimensions, context) { + const {geometry: G, r: R} = channels; + const path = d3.geoPath(context.projection ?? scaleProjection(scales)); + const {r} = this; + if (negative(r)) index = []; + else if (r !== undefined) path.pointRadius(r); + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, scales) + .call((g) => { + g.selectAll() + .data(index) + .enter() + .append("path") + .call(applyDirectStyles, this) + .attr("d", R ? (i) => path.pointRadius(R[i])(G[i]) : (i) => path(G[i])) + .call(applyChannelStyles, this, channels); + }) + .node(); + } +} + +// If no projection is specified, default to a projection that passes points +// through the x and y scales, if any. +function scaleProjection({x: X, y: Y}) { + if (X || Y) { + X ??= (x) => x; + Y ??= (y) => y; + return d3.geoTransform({ + point(x, y) { + this.stream.point(X(x), Y(y)); + } + }); + } +} + +function geo(data, {geometry = identity$1, ...options} = {}) { + switch (data?.type) { + case "FeatureCollection": + data = data.features; + break; + case "GeometryCollection": + data = data.geometries; + break; + case "Feature": + case "LineString": + case "MultiLineString": + case "MultiPoint": + case "MultiPolygon": + case "Point": + case "Polygon": + case "Sphere": + data = [data]; + break; + } + return new Geo(data, {geometry, ...options}); +} + +function sphere({strokeWidth = 1.5, ...options} = {}) { + return geo({type: "Sphere"}, {strokeWidth, ...options}); +} + +function graticule({strokeOpacity = 0.1, ...options} = {}) { + return geo(d3.geoGraticule10(), {strokeOpacity, ...options}); +} + +// We don’t want the hexagons to align with the edges of the plot frame, as that +// would cause extreme x-values (the upper bound of the default x-scale domain) +// to be rounded up into a floating bin to the right of the plot. Therefore, +// rather than centering the origin hexagon around ⟨0,0⟩ in screen coordinates, +// we offset slightly to ⟨0.5,0⟩. The hexgrid mark uses the same origin. +const ox = 0.5, + oy = 0; + +function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) { + const {z} = options; + + // TODO filter e.g. to show empty hexbins? + binWidth = binWidth === undefined ? 20 : number$1(binWidth); + outputs = maybeGroupOutputs(outputs, options); + + // A fill output means a fill channel; declaring the channel here instead of + // waiting for the initializer allows the mark constructor to determine that + // the stroke should default to none (assuming a mark that defaults to fill + // and no stroke, such as dot). Note that it’s safe to mutate options here + // because we just created it with the rest operator above. + if (hasOutput(outputs, "fill")) options.channels = {...options.channels, fill: {value: []}}; + + // Populate default values for the r and symbol options, as appropriate. + if (options.symbol === undefined) options.symbol = "hexagon"; + if (options.r === undefined && !hasOutput(outputs, "r")) options.r = binWidth / 2; + + return initializer(options, (data, facets, channels, scales, _, context) => { + let {x: X, y: Y, z: Z, fill: F, stroke: S, symbol: Q} = channels; + if (X === undefined) throw new Error("missing channel: x"); + if (Y === undefined) throw new Error("missing channel: y"); + + // Get the (either scaled or projected) xy channels. + ({x: X, y: Y} = applyPosition(channels, scales, context)); + + // Extract the values for channels that are eligible for grouping; not all + // marks define a z channel, so compute one if it not already computed. If z + // was explicitly set to null, ensure that we don’t subdivide bins. + Z = Z ? Z.value : valueof(data, z); + F = F?.value; + S = S?.value; + Q = Q?.value; + + // Group on the first of z, fill, stroke, and symbol. Implicitly reduce + // these channels using the first corresponding value for each bin. + const G = maybeSubgroup(outputs, {z: Z, fill: F, stroke: S, symbol: Q}); + const GZ = Z && []; + const GF = F && []; + const GS = S && []; + const GQ = Q && []; + + // Construct the hexbins and populate the output channels. + const binFacets = []; + const BX = []; + const BY = []; + let i = -1; + for (const o of outputs) o.initialize(data); + for (const facet of facets) { + const binFacet = []; + for (const o of outputs) o.scope("facet", facet); + for (const [f, I] of maybeGroup(facet, G)) { + for (const {index: b, extent} of hbin(data, I, X, Y, binWidth)) { + binFacet.push(++i); + BX.push(extent.x); + BY.push(extent.y); + if (Z) GZ.push(G === Z ? f : Z[b[0]]); + if (F) GF.push(G === F ? f : F[b[0]]); + if (S) GS.push(G === S ? f : S[b[0]]); + if (Q) GQ.push(G === Q ? f : Q[b[0]]); + for (const o of outputs) o.reduce(b, extent); + } + } + binFacets.push(binFacet); + } + + // Construct the output channels, and populate the radius scale hint. + const sx = channels.x.scale; + const sy = channels.y.scale; + const binChannels = { + x: {value: BX, source: scales[sx] ? {value: map$1(BX, scales[sx].invert), scale: sx} : null}, + y: {value: BY, source: scales[sy] ? {value: map$1(BY, scales[sy].invert), scale: sy} : null}, + ...(Z && {z: {value: GZ}}), + ...(F && {fill: {value: GF, scale: "auto"}}), + ...(S && {stroke: {value: GS, scale: "auto"}}), + ...(Q && {symbol: {value: GQ, scale: "auto"}}), + ...Object.fromEntries( + outputs.map(({name, output}) => [ + name, + { + scale: "auto", + label: output.label, + radius: name === "r" ? binWidth / 2 : undefined, + value: output.transform() + } + ]) + ) + }; + + return {data, facets: binFacets, channels: binChannels}; + }); +} + +function hbin(data, I, X, Y, dx) { + const dy = dx * (1.5 / sqrt3); + const bins = new Map(); + for (const i of I) { + let px = X[i], + py = Y[i]; + if (isNaN(px) || isNaN(py)) continue; + let pj = Math.round((py = (py - oy) / dy)), + pi = Math.round((px = (px - ox) / dx - (pj & 1) / 2)), + py1 = py - pj; + if (Math.abs(py1) * 3 > 1) { + let px1 = px - pi, + pi2 = pi + (px < pi ? -1 : 1) / 2, + pj2 = pj + (py < pj ? -1 : 1), + px2 = px - pi2, + py2 = py - pj2; + if (px1 * px1 + py1 * py1 > px2 * px2 + py2 * py2) (pi = pi2 + (pj & 1 ? 1 : -1) / 2), (pj = pj2); + } + const key = `${pi},${pj}`; + let bin = bins.get(key); + if (bin === undefined) { + bin = {index: [], extent: {data, x: (pi + (pj & 1) / 2) * dx + ox, y: pj * dy + oy}}; + bins.set(key, bin); + } + bin.index.push(i); + } + return bins.values(); +} + +const defaults$2 = { + ariaLabel: "hexgrid", + fill: "none", + stroke: "currentColor", + strokeOpacity: 0.1 +}; + +function hexgrid(options) { + return new Hexgrid(options); +} + +class Hexgrid extends Mark { + constructor({binWidth = 20, clip = true, ...options} = {}) { + super(singleton, undefined, {clip, ...options}, defaults$2); + this.binWidth = number$1(binWidth); + } + render(index, scales, channels, dimensions, context) { + const {binWidth} = this; + const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions; + const x0 = marginLeft - ox, + x1 = width - marginRight - ox, + y0 = marginTop - oy, + y1 = height - marginBottom - oy, + rx = binWidth / 2, + ry = rx * sqrt4_3, + hy = ry / 2, + wx = rx * 2, + wy = ry * 1.5, + i0 = Math.floor(x0 / wx), + i1 = Math.ceil(x1 / wx), + j0 = Math.floor((y0 + hy) / wy), + j1 = Math.ceil((y1 - hy) / wy) + 1, + path = `m0,${round(-ry)}l${round(rx)},${round(hy)}v${round(ry)}l${round(-rx)},${round(hy)}`; + let d = path; + for (let j = j0; j < j1; ++j) { + for (let i = i0; i < i1; ++i) { + d += `M${round(i * wx + (j & 1) * rx)},${round(j * wy)}${path}`; + } + } + return create("svg:g", context) + .datum(0) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {}, offset + ox, offset + oy) + .call((g) => g.append("path").call(applyDirectStyles, this).call(applyChannelStyles, this, channels).attr("d", d)) + .node(); + } +} + +function round(x) { + return Math.round(x * 1e3) / 1e3; +} + +const defaults$1 = { + ariaLabel: "image", + fill: null, + stroke: null +}; + +// Tests if the given string is a path: does it start with a dot-slash +// (./foo.png), dot-dot-slash (../foo.png), or slash (/foo.png)? +function isPath(string) { + return /^\.*\//.test(string); +} + +// Tests if the given string is a URL (e.g., https://placekitten.com/200/300). +// The allowed protocols is overly restrictive, but we don’t want to allow any +// scheme here because it would increase the likelihood of a false positive with +// a field name that happens to contain a colon. +function isUrl(string) { + return /^(blob|data|file|http|https):/i.test(string); +} + +// Disambiguates a constant src definition from a channel. A path or URL string +// is assumed to be a constant; any other string is assumed to be a field name. +function maybePathChannel(value) { + return typeof value === "string" && (isPath(value) || isUrl(value)) ? [undefined, value] : [value, undefined]; +} + +class Image extends Mark { + constructor(data, options = {}) { + let {x, y, r, width, height, rotate, src, preserveAspectRatio, crossOrigin, frameAnchor, imageRendering} = options; + if (r == null) r = undefined; + if (r === undefined && width === undefined && height === undefined) width = height = 16; + else if (width === undefined && height !== undefined) width = height; + else if (height === undefined && width !== undefined) height = width; + const [vs, cs] = maybePathChannel(src); + const [vr, cr] = maybeNumberChannel(r); + const [vw, cw] = maybeNumberChannel(width, cr !== undefined ? cr * 2 : undefined); + const [vh, ch] = maybeNumberChannel(height, cr !== undefined ? cr * 2 : undefined); + const [va, ca] = maybeNumberChannel(rotate, 0); + super( + data, + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + r: {value: vr, scale: "r", filter: positive, optional: true}, + width: {value: vw, filter: positive, optional: true}, + height: {value: vh, filter: positive, optional: true}, + rotate: {value: va, optional: true}, + src: {value: vs, optional: true} + }, + withDefaultSort(options), + defaults$1 + ); + this.src = cs; + this.width = cw; + this.rotate = ca; + this.height = ch; + this.r = cr; + this.preserveAspectRatio = impliedString(preserveAspectRatio, "xMidYMid"); + this.crossOrigin = string(crossOrigin); + this.frameAnchor = maybeFrameAnchor(frameAnchor); + this.imageRendering = impliedString(imageRendering, "auto"); + } + render(index, scales, channels, dimensions, context) { + const {x, y} = scales; + const {x: X, y: Y, width: W, height: H, r: R, rotate: A, src: S} = channels; + const {r, width, height, rotate} = this; + const [cx, cy] = applyFrameAnchor(this, dimensions); + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, {x: X && x, y: Y && y}) + .call((g) => + g + .selectAll() + .data(index) + .enter() + .append("image") + .call(applyDirectStyles, this) + .attr("x", position(X, W, R, cx, width, r)) + .attr("y", position(Y, H, R, cy, height, r)) + .attr("width", W ? (i) => W[i] : width !== undefined ? width : R ? (i) => R[i] * 2 : r * 2) + .attr("height", H ? (i) => H[i] : height !== undefined ? height : R ? (i) => R[i] * 2 : r * 2) + // TODO: combine x, y, rotate and transform-origin into a single transform + .attr("transform", A ? (i) => `rotate(${A[i]})` : rotate ? `rotate(${rotate})` : null) + .attr("transform-origin", A || rotate ? template`${X ? (i) => X[i] : cx}px ${Y ? (i) => Y[i] : cy}px` : null) + .call(applyAttr, "href", S ? (i) => S[i] : this.src) + .call(applyAttr, "preserveAspectRatio", this.preserveAspectRatio) + .call(applyAttr, "crossorigin", this.crossOrigin) + .call(applyAttr, "image-rendering", this.imageRendering) + .call(applyAttr, "clip-path", R ? (i) => `circle(${R[i]}px)` : r !== undefined ? `circle(${r}px)` : null) + .call(applyChannelStyles, this, channels) + ) + .node(); + } +} + +function position(X, W, R, x, w, r) { + return W && X + ? (i) => X[i] - W[i] / 2 + : W + ? (i) => x - W[i] / 2 + : X && w !== undefined + ? (i) => X[i] - w / 2 + : w !== undefined + ? x - w / 2 + : R && X + ? (i) => X[i] - R[i] + : R + ? (i) => x - R[i] + : X + ? (i) => X[i] - r + : x - r; +} + +function image(data, {x, y, ...options} = {}) { + if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); + return new Image(data, {...options, x, y}); +} + +// https://github.com/jstat/jstat +// +// Copyright (c) 2013 jStat +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +function ibetainv(p, a, b) { + var EPS = 1e-8; + var a1 = a - 1; + var b1 = b - 1; + var j = 0; + var lna, lnb, pp, t, u, err, x, al, h, w, afac; + if (p <= 0) return 0; + if (p >= 1) return 1; + if (a >= 1 && b >= 1) { + pp = p < 0.5 ? p : 1 - p; + t = Math.sqrt(-2 * Math.log(pp)); + x = (2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t; + if (p < 0.5) x = -x; + al = (x * x - 3) / 6; + h = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1)); + w = (x * Math.sqrt(al + h)) / h - (1 / (2 * b - 1) - 1 / (2 * a - 1)) * (al + 5 / 6 - 2 / (3 * h)); + x = a / (a + b * Math.exp(2 * w)); + } else { + lna = Math.log(a / (a + b)); + lnb = Math.log(b / (a + b)); + t = Math.exp(a * lna) / a; + u = Math.exp(b * lnb) / b; + w = t + u; + if (p < t / w) x = Math.pow(a * w * p, 1 / a); + else x = 1 - Math.pow(b * w * (1 - p), 1 / b); + } + afac = -gammaln(a) - gammaln(b) + gammaln(a + b); + for (; j < 10; j++) { + if (x === 0 || x === 1) return x; + err = ibeta(x, a, b) - p; + t = Math.exp(a1 * Math.log(x) + b1 * Math.log(1 - x) + afac); + u = err / t; + x -= t = u / (1 - 0.5 * Math.min(1, u * (a1 / x - b1 / (1 - x)))); + if (x <= 0) x = 0.5 * (x + t); + if (x >= 1) x = 0.5 * (x + t + 1); + if (Math.abs(t) < EPS * x && j > 0) break; + } + return x; +} + +function ibeta(x, a, b) { + // Factors in front of the continued fraction. + var bt = + x === 0 || x === 1 ? 0 : Math.exp(gammaln(a + b) - gammaln(a) - gammaln(b) + a * Math.log(x) + b * Math.log(1 - x)); + if (x < 0 || x > 1) return false; + if (x < (a + 1) / (a + b + 2)) + // Use continued fraction directly. + return (bt * betacf(x, a, b)) / a; + // else use continued fraction after making the symmetry transformation. + return 1 - (bt * betacf(1 - x, b, a)) / b; +} + +function betacf(x, a, b) { + var fpmin = 1e-30; + var m = 1; + var qab = a + b; + var qap = a + 1; + var qam = a - 1; + var c = 1; + var d = 1 - (qab * x) / qap; + var m2, aa, del, h; + + // These q's will be used in factors that occur in the coefficients + if (Math.abs(d) < fpmin) d = fpmin; + d = 1 / d; + h = d; + + for (; m <= 100; m++) { + m2 = 2 * m; + aa = (m * (b - m) * x) / ((qam + m2) * (a + m2)); + // One step (the even one) of the recurrence + d = 1 + aa * d; + if (Math.abs(d) < fpmin) d = fpmin; + c = 1 + aa / c; + if (Math.abs(c) < fpmin) c = fpmin; + d = 1 / d; + h *= d * c; + aa = (-(a + m) * (qab + m) * x) / ((a + m2) * (qap + m2)); + // Next step of the recurrence (the odd one) + d = 1 + aa * d; + if (Math.abs(d) < fpmin) d = fpmin; + c = 1 + aa / c; + if (Math.abs(c) < fpmin) c = fpmin; + d = 1 / d; + del = d * c; + h *= del; + if (Math.abs(del - 1.0) < 3e-7) break; + } + + return h; +} + +function gammaln(x) { + var j = 0; + var cof = [ + 76.18009172947146, -86.5053203294167, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, + -0.5395239384953e-5 + ]; + var ser = 1.000000000190015; + var xx, y, tmp; + tmp = (y = xx = x) + 5.5; + tmp -= (xx + 0.5) * Math.log(tmp); + for (; j < 6; j++) ser += cof[j] / ++y; + return Math.log((2.506628274631 * ser) / xx) - tmp; +} + +function qt(p, dof) { + var x = ibetainv(2 * Math.min(p, 1 - p), 0.5 * dof, 0.5); + x = Math.sqrt((dof * (1 - x)) / x); + return p > 0.5 ? x : -x; +} + +const defaults = { + ariaLabel: "linear-regression", + fill: "currentColor", + fillOpacity: 0.1, + stroke: "currentColor", + strokeWidth: 1.5, + strokeLinecap: "round", + strokeLinejoin: "round", + strokeMiterlimit: 1 +}; + +class LinearRegression extends Mark { + constructor(data, options = {}) { + const {x, y, z, ci = 0.95, precision = 4} = options; + super( + data, + { + x: {value: x, scale: "x"}, + y: {value: y, scale: "y"}, + z: {value: maybeZ(options), optional: true} + }, + options, + defaults + ); + this.z = z; + this.ci = +ci; + this.precision = +precision; + if (!(0 <= this.ci && this.ci < 1)) throw new Error(`invalid ci; not in [0, 1): ${ci}`); + if (!(this.precision > 0)) throw new Error(`invalid precision: ${precision}`); + } + render(index, scales, channels, dimensions, context) { + const {x: X, y: Y, z: Z} = channels; + const {ci} = this; + return create("svg:g", context) + .call(applyIndirectStyles, this, dimensions, context) + .call(applyTransform, this, scales) + .call((g) => + g + .selectAll() + .data(Z ? groupZ(index, Z, this.z) : [index]) + .enter() + .call((enter) => + enter + .append("path") + .attr("fill", "none") + .call(applyDirectStyles, this) + .call(applyGroupedChannelStyles, this, {...channels, fill: null, fillOpacity: null}) + .attr("d", (I) => this._renderLine(I, X, Y)) + .call( + ci && !isNone(this.fill) + ? (path) => + path + .select(pathBefore) + .attr("stroke", "none") + .call(applyDirectStyles, this) + .call(applyGroupedChannelStyles, this, { + ...channels, + stroke: null, + strokeOpacity: null, + strokeWidth: null + }) + .attr("d", (I) => this._renderBand(I, X, Y)) + : () => {} + ) + ) + ) + .node(); + } +} + +function pathBefore() { + return this.parentNode.insertBefore(this.ownerDocument.createElementNS(d3.namespaces.svg, "path"), this); +} + +class LinearRegressionX extends LinearRegression { + constructor(data, options) { + super(data, options); + } + _renderBand(I, X, Y) { + const {ci, precision} = this; + const [y1, y2] = d3.extent(I, (i) => Y[i]); + const f = linearRegressionF(I, Y, X); + const g = confidenceIntervalF(I, Y, X, (1 - ci) / 2, f); + return d3.area() + .y((y) => y) + .x0((y) => g(y, -1)) + .x1((y) => g(y, +1))(d3.range(y1, y2 - precision / 2, precision).concat(y2)); + } + _renderLine(I, X, Y) { + const [y1, y2] = d3.extent(I, (i) => Y[i]); + const f = linearRegressionF(I, Y, X); + return `M${f(y1)},${y1}L${f(y2)},${y2}`; + } +} + +class LinearRegressionY extends LinearRegression { + constructor(data, options) { + super(data, options); + } + _renderBand(I, X, Y) { + const {ci, precision} = this; + const [x1, x2] = d3.extent(I, (i) => X[i]); + const f = linearRegressionF(I, X, Y); + const g = confidenceIntervalF(I, X, Y, (1 - ci) / 2, f); + return d3.area() + .x((x) => x) + .y0((x) => g(x, -1)) + .y1((x) => g(x, +1))(d3.range(x1, x2 - precision / 2, precision).concat(x2)); + } + _renderLine(I, X, Y) { + const [x1, x2] = d3.extent(I, (i) => X[i]); + const f = linearRegressionF(I, X, Y); + return `M${x1},${f(x1)}L${x2},${f(x2)}`; + } +} + +function linearRegressionX( + data, + {y = indexOf, x = identity$1, stroke, fill = isNoneish(stroke) ? "currentColor" : stroke, ...options} = {} +) { + return new LinearRegressionX(data, maybeDenseIntervalY({...options, x, y, fill, stroke})); +} + +function linearRegressionY( + data, + {x = indexOf, y = identity$1, stroke, fill = isNoneish(stroke) ? "currentColor" : stroke, ...options} = {} +) { + return new LinearRegressionY(data, maybeDenseIntervalX({...options, x, y, fill, stroke})); +} + +function linearRegressionF(I, X, Y) { + let sumX = 0, + sumY = 0, + sumXY = 0, + sumX2 = 0; + for (const i of I) { + const xi = X[i]; + const yi = Y[i]; + sumX += xi; + sumY += yi; + sumXY += xi * yi; + sumX2 += xi * xi; + } + const n = I.length; + const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); + const intercept = (sumY - slope * sumX) / n; + return (x) => slope * x + intercept; +} + +function confidenceIntervalF(I, X, Y, p, f) { + const mean = d3.sum(I, (i) => X[i]) / I.length; + let a = 0, + b = 0; + for (const i of I) { + a += (X[i] - mean) ** 2; + b += (Y[i] - f(X[i])) ** 2; + } + const sy = Math.sqrt(b / (I.length - 2)); + const t = qt(p, I.length - 2); + return (x, k) => { + const Y = f(x); + const se = sy * Math.sqrt(1 / I.length + (x - mean) ** 2 / a); + return Y + k * t * se; + }; +} + +function treeNode({ + path = identity$1, // the delimited path + delimiter, // how the path is separated + frameAnchor, + treeLayout = d3.tree, + treeSort, + treeSeparation, + treeAnchor, + treeFilter, + ...options +} = {}) { + treeAnchor = maybeTreeAnchor(treeAnchor); + treeSort = maybeTreeSort(treeSort); + if (treeFilter != null) treeFilter = maybeNodeValue(treeFilter); + if (frameAnchor === undefined) frameAnchor = treeAnchor.frameAnchor; + const normalize = normalizer(delimiter); + const outputs = treeOutputs(options, maybeNodeValue); + const [X, setX] = column(); + const [Y, setY] = column(); + return { + x: X, + y: Y, + frameAnchor, + ...basic(options, (data, facets) => { + const P = normalize(valueof(data, path)); + const X = setX([]); + const Y = setY([]); + let treeIndex = -1; + const treeData = []; + const treeFacets = []; + const rootof = d3.stratify().path((i) => P[i]); + const layout = treeLayout(); + if (layout.nodeSize) layout.nodeSize([1, 1]); + if (layout.separation && treeSeparation !== undefined) layout.separation(treeSeparation ?? one); + for (const o of outputs) o[output_values] = o[output_setValues]([]); + for (const facet of facets) { + const treeFacet = []; + const root = rootof(facet.filter((i) => P[i] != null)).each((node) => (node.data = data[node.data])); + if (treeSort != null) root.sort(treeSort); + layout(root); + for (const node of root.descendants()) { + if (treeFilter != null && !treeFilter(node)) continue; + treeFacet.push(++treeIndex); + treeData[treeIndex] = node.data; + treeAnchor.position(node, treeIndex, X, Y); + for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](node); + } + treeFacets.push(treeFacet); + } + return {data: treeData, facets: treeFacets}; + }), + ...Object.fromEntries(outputs) + }; +} + +function treeLink({ + path = identity$1, // the delimited path + delimiter, // how the path is separated + curve = "bump-x", + stroke = "#555", + strokeWidth = 1.5, + strokeOpacity = 0.5, + treeLayout = d3.tree, + treeSort, + treeSeparation, + treeAnchor, + treeFilter, + ...options +} = {}) { + treeAnchor = maybeTreeAnchor(treeAnchor); + treeSort = maybeTreeSort(treeSort); + if (treeFilter != null) treeFilter = maybeLinkValue(treeFilter); + options = {curve, stroke, strokeWidth, strokeOpacity, ...options}; + const normalize = normalizer(delimiter); + const outputs = treeOutputs(options, maybeLinkValue); + const [X1, setX1] = column(); + const [X2, setX2] = column(); + const [Y1, setY1] = column(); + const [Y2, setY2] = column(); + return { + x1: X1, + x2: X2, + y1: Y1, + y2: Y2, + ...basic(options, (data, facets) => { + const P = normalize(valueof(data, path)); + const X1 = setX1([]); + const X2 = setX2([]); + const Y1 = setY1([]); + const Y2 = setY2([]); + let treeIndex = -1; + const treeData = []; + const treeFacets = []; + const rootof = d3.stratify().path((i) => P[i]); + const layout = treeLayout(); + if (layout.nodeSize) layout.nodeSize([1, 1]); + if (layout.separation && treeSeparation !== undefined) layout.separation(treeSeparation ?? one); + for (const o of outputs) o[output_values] = o[output_setValues]([]); + for (const facet of facets) { + const treeFacet = []; + const root = rootof(facet.filter((i) => P[i] != null)).each((node) => (node.data = data[node.data])); + if (treeSort != null) root.sort(treeSort); + layout(root); + for (const {source, target} of root.links()) { + if (treeFilter != null && !treeFilter(target, source)) continue; + treeFacet.push(++treeIndex); + treeData[treeIndex] = target.data; + treeAnchor.position(source, treeIndex, X1, Y1); + treeAnchor.position(target, treeIndex, X2, Y2); + for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](target, source); + } + treeFacets.push(treeFacet); + } + return {data: treeData, facets: treeFacets}; + }), + ...Object.fromEntries(outputs) + }; +} + +function maybeTreeAnchor(anchor = "left") { + switch (`${anchor}`.trim().toLowerCase()) { + case "left": + return treeAnchorLeft; + case "right": + return treeAnchorRight; + } + throw new Error(`invalid tree anchor: ${anchor}`); +} + +const treeAnchorLeft = { + frameAnchor: "left", + dx: 6, + position({x, y}, i, X, Y) { + X[i] = y; + Y[i] = -x; + } +}; + +const treeAnchorRight = { + frameAnchor: "right", + dx: -6, + position({x, y}, i, X, Y) { + X[i] = -y; + Y[i] = -x; + } +}; + +function maybeTreeSort(sort) { + return sort == null || typeof sort === "function" + ? sort + : `${sort}`.trim().toLowerCase().startsWith("node:") + ? nodeSort(maybeNodeValue(sort)) + : nodeSort(nodeData(sort)); +} + +function nodeSort(value) { + return (a, b) => ascendingDefined(value(a), value(b)); +} + +function nodeData(field) { + return (node) => node.data?.[field]; +} + +function normalizer(delimiter = "/") { + delimiter = `${delimiter}`; + if (delimiter === "/") return (P) => P; // paths are already slash-separated + if (delimiter.length !== 1) throw new Error("delimiter must be exactly one character"); + const delimiterCode = delimiter.charCodeAt(0); + return (P) => P.map((p) => slashDelimiter(p, delimiterCode)); +} + +const CODE_BACKSLASH = 92; +const CODE_SLASH = 47; + +function slashDelimiter(input, delimiterCode) { + if (delimiterCode === CODE_BACKSLASH) throw new Error("delimiter cannot be backslash"); + let afterBackslash = false; + for (let i = 0, n = input.length; i < n; ++i) { + switch (input.charCodeAt(i)) { + case CODE_BACKSLASH: + if (!afterBackslash) { + afterBackslash = true; + continue; + } + break; + case delimiterCode: + if (afterBackslash) { + (input = input.slice(0, i - 1) + input.slice(i)), --i, --n; // remove backslash + } else { + input = input.slice(0, i) + "/" + input.slice(i + 1); // replace delimiter with slash + } + break; + case CODE_SLASH: + if (afterBackslash) { + (input = input.slice(0, i) + "\\\\" + input.slice(i)), (i += 2), (n += 2); // add two backslashes + } else { + (input = input.slice(0, i) + "\\" + input.slice(i)), ++i, ++n; // add backslash + } + break; + } + afterBackslash = false; + } + return input; +} + +function slashUnescape(input) { + let afterBackslash = false; + for (let i = 0, n = input.length; i < n; ++i) { + switch (input.charCodeAt(i)) { + case CODE_BACKSLASH: + if (!afterBackslash) { + afterBackslash = true; + continue; + } + // eslint-disable-next-line no-fallthrough + case CODE_SLASH: + if (afterBackslash) { + (input = input.slice(0, i - 1) + input.slice(i)), --i, --n; // remove backslash + } + break; + } + afterBackslash = false; + } + return input; +} + +function isNodeValue(option) { + return isObject(option) && typeof option.node === "function"; +} + +function isLinkValue(option) { + return isObject(option) && typeof option.link === "function"; +} + +function maybeNodeValue(value) { + if (isNodeValue(value)) return value.node; + value = `${value}`.trim().toLowerCase(); + if (!value.startsWith("node:")) return; + switch (value) { + case "node:name": + return nodeName; + case "node:path": + return nodePath; + case "node:internal": + return nodeInternal; + case "node:external": + return nodeExternal; + case "node:depth": + return nodeDepth; + case "node:height": + return nodeHeight; + } + throw new Error(`invalid node value: ${value}`); +} + +function maybeLinkValue(value) { + if (isNodeValue(value)) return value.node; + if (isLinkValue(value)) return value.link; + value = `${value}`.trim().toLowerCase(); + if (!value.startsWith("node:") && !value.startsWith("parent:")) return; + switch (value) { + case "parent:name": + return parentValue(nodeName); + case "parent:path": + return parentValue(nodePath); + case "parent:depth": + return parentValue(nodeDepth); + case "parent:height": + return parentValue(nodeHeight); + case "node:name": + return nodeName; + case "node:path": + return nodePath; + case "node:internal": + return nodeInternal; + case "node:external": + return nodeExternal; + case "node:depth": + return nodeDepth; + case "node:height": + return nodeHeight; + } + throw new Error(`invalid link value: ${value}`); +} + +function nodePath(node) { + return node.id; +} + +function nodeName(node) { + return nameof(node.id); +} + +function nodeDepth(node) { + return node.depth; +} + +function nodeHeight(node) { + return node.height; +} + +function nodeInternal(node) { + return !!node.children; +} + +function nodeExternal(node) { + return !node.children; +} + +function parentValue(evaluate) { + return (child, parent) => (parent == null ? undefined : evaluate(parent)); +} + +// Walk backwards to find the first slash. +function nameof(path) { + let i = path.length; + while (--i > 0) if (slash(path, i)) break; + return slashUnescape(path.slice(i + 1)); +} + +// Slashes can be escaped; to determine whether a slash is a path delimiter, we +// count the number of preceding backslashes escaping the forward slash: an odd +// number indicates an escaped forward slash. +function slash(path, i) { + if (path[i] === "/") { + let k = 0; + while (i > 0 && path[--i] === "\\") ++k; + if ((k & 1) === 0) return true; + } + return false; +} + +// These indexes match the array returned by nodeOutputs. The first two elements +// are always the name of the output and its column value definition so that +// the outputs can be passed directly to Object.fromEntries. +const output_setValues = 2; +const output_evaluate = 3; +const output_values = 4; + +function treeOutputs(options, maybeTreeValue) { + const outputs = []; + for (const name in options) { + const value = options[name]; + const treeValue = maybeTreeValue(value); + if (treeValue !== undefined) { + outputs.push([name, ...column(value), treeValue]); + } + } + return outputs; +} + +function tree( + data, + { + fill, + stroke, + strokeWidth, + strokeOpacity, + strokeLinejoin, + strokeLinecap, + strokeMiterlimit, + strokeDasharray, + strokeDashoffset, + marker, + markerStart = marker, + markerEnd = marker, + dot: dotDot = isNoneish(markerStart) && isNoneish(markerEnd), + text: textText = "node:name", + textStroke = "var(--plot-background)", + title = "node:path", + dx, + dy, + textAnchor, + treeLayout = d3.tree, + textLayout = treeLayout === d3.tree || treeLayout === d3.cluster ? "mirrored" : "normal", + tip, + ...options + } = {} +) { + if (dx === undefined) dx = maybeTreeAnchor(options.treeAnchor).dx; + if (textAnchor !== undefined) throw new Error("textAnchor is not a configurable tree option"); + textLayout = keyword(textLayout, "textLayout", ["mirrored", "normal"]); + + function treeText(textOptions) { + return text( + data, + treeNode({ + treeLayout, + text: textText, + fill: fill === undefined ? "currentColor" : fill, + stroke: textStroke, + dx, + dy, + title, + ...textOptions, + ...options + }) + ); + } + + return marks( + link( + data, + treeLink({ + treeLayout, + markerStart, + markerEnd, + stroke: stroke !== undefined ? stroke : fill === undefined ? "node:internal" : fill, + strokeWidth, + strokeOpacity, + strokeLinejoin, + strokeLinecap, + strokeMiterlimit, + strokeDasharray, + strokeDashoffset, + ...options + }) + ), + dotDot + ? dot(data, treeNode({treeLayout, fill: fill === undefined ? "node:internal" : fill, title, tip, ...options})) + : null, + textText != null + ? textLayout === "mirrored" + ? [ + treeText({textAnchor: "start", treeFilter: "node:external"}), + treeText({textAnchor: "end", treeFilter: "node:internal", dx: -dx}) + ] + : treeText() + : null + ); +} + +function cluster(data, options) { + return tree(data, {...options, treeLayout: d3.cluster}); +} + +function centroid({geometry = identity$1, ...options} = {}) { + // Suppress defaults for x and y since they will be computed by the initializer. + return initializer({...options, x: null, y: null}, (data, facets, channels, scales, dimensions, {projection}) => { + const G = valueof(data, geometry); + const n = G.length; + const X = new Float64Array(n); + const Y = new Float64Array(n); + const path = d3.geoPath(projection); + for (let i = 0; i < n; ++i) [X[i], Y[i]] = path.centroid(G[i]); + return { + data, + facets, + channels: { + x: {value: X, scale: projection == null ? "x" : null, source: null}, + y: {value: Y, scale: projection == null ? "y" : null, source: null} + } + }; + }); +} + +function geoCentroid({geometry = identity$1, ...options} = {}) { + let C; + return { + ...options, + x: {transform: (data) => Float64Array.from((C = valueof(valueof(data, geometry), d3.geoCentroid)), ([x]) => x)}, + y: {transform: () => Float64Array.from(C, ([, y]) => y)} + }; +} + +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} + +// (a, y, c, l, h) = (array, y[, cmp, lo, hi]) + +function ge(a, y, c, l, h) { + var i = h + 1; + while (l <= h) { + var m = (l + h) >>> 1, x = a[m]; + var p = (c !== undefined) ? c(x, y) : (x - y); + if (p >= 0) { i = m; h = m - 1; } else { l = m + 1; } + } + return i; +} +function gt(a, y, c, l, h) { + var i = h + 1; + while (l <= h) { + var m = (l + h) >>> 1, x = a[m]; + var p = (c !== undefined) ? c(x, y) : (x - y); + if (p > 0) { i = m; h = m - 1; } else { l = m + 1; } + } + return i; +} +function lt(a, y, c, l, h) { + var i = l - 1; + while (l <= h) { + var m = (l + h) >>> 1, x = a[m]; + var p = (c !== undefined) ? c(x, y) : (x - y); + if (p < 0) { i = m; l = m + 1; } else { h = m - 1; } + } + return i; +} +function le(a, y, c, l, h) { + var i = l - 1; + while (l <= h) { + var m = (l + h) >>> 1, x = a[m]; + var p = (c !== undefined) ? c(x, y) : (x - y); + if (p <= 0) { i = m; l = m + 1; } else { h = m - 1; } + } + return i; +} +function eq(a, y, c, l, h) { + while (l <= h) { + var m = (l + h) >>> 1, x = a[m]; + var p = (c !== undefined) ? c(x, y) : (x - y); + if (p === 0) { return m } + if (p <= 0) { l = m + 1; } else { h = m - 1; } + } + return -1; +} +function norm(a, y, c, l, h, f) { + if (typeof c === 'function') { + return f(a, y, c, (l === undefined) ? 0 : l | 0, (h === undefined) ? a.length - 1 : h | 0); + } + return f(a, y, undefined, (c === undefined) ? 0 : c | 0, (l === undefined) ? a.length - 1 : l | 0); +} + +var searchBounds = { + ge: function(a, y, c, l, h) { return norm(a, y, c, l, h, ge)}, + gt: function(a, y, c, l, h) { return norm(a, y, c, l, h, gt)}, + lt: function(a, y, c, l, h) { return norm(a, y, c, l, h, lt)}, + le: function(a, y, c, l, h) { return norm(a, y, c, l, h, le)}, + eq: function(a, y, c, l, h) { return norm(a, y, c, l, h, eq)} +}; + +var bounds = searchBounds; + +var NOT_FOUND = 0; +var SUCCESS = 1; +var EMPTY = 2; + +var intervalTree = createWrapper; + +function IntervalTreeNode(mid, left, right, leftPoints, rightPoints) { + this.mid = mid; + this.left = left; + this.right = right; + this.leftPoints = leftPoints; + this.rightPoints = rightPoints; + this.count = (left ? left.count : 0) + (right ? right.count : 0) + leftPoints.length; +} + +var proto = IntervalTreeNode.prototype; + +function copy(a, b) { + a.mid = b.mid; + a.left = b.left; + a.right = b.right; + a.leftPoints = b.leftPoints; + a.rightPoints = b.rightPoints; + a.count = b.count; +} + +function rebuild(node, intervals) { + var ntree = createIntervalTree(intervals); + node.mid = ntree.mid; + node.left = ntree.left; + node.right = ntree.right; + node.leftPoints = ntree.leftPoints; + node.rightPoints = ntree.rightPoints; + node.count = ntree.count; +} + +function rebuildWithInterval(node, interval) { + var intervals = node.intervals([]); + intervals.push(interval); + rebuild(node, intervals); +} + +function rebuildWithoutInterval(node, interval) { + var intervals = node.intervals([]); + var idx = intervals.indexOf(interval); + if(idx < 0) { + return NOT_FOUND + } + intervals.splice(idx, 1); + rebuild(node, intervals); + return SUCCESS +} + +proto.intervals = function(result) { + result.push.apply(result, this.leftPoints); + if(this.left) { + this.left.intervals(result); + } + if(this.right) { + this.right.intervals(result); + } + return result +}; + +proto.insert = function(interval) { + var weight = this.count - this.leftPoints.length; + this.count += 1; + if(interval[1] < this.mid) { + if(this.left) { + if(4*(this.left.count+1) > 3*(weight+1)) { + rebuildWithInterval(this, interval); + } else { + this.left.insert(interval); + } + } else { + this.left = createIntervalTree([interval]); + } + } else if(interval[0] > this.mid) { + if(this.right) { + if(4*(this.right.count+1) > 3*(weight+1)) { + rebuildWithInterval(this, interval); + } else { + this.right.insert(interval); + } + } else { + this.right = createIntervalTree([interval]); + } + } else { + var l = bounds.ge(this.leftPoints, interval, compareBegin); + var r = bounds.ge(this.rightPoints, interval, compareEnd); + this.leftPoints.splice(l, 0, interval); + this.rightPoints.splice(r, 0, interval); + } +}; + +proto.remove = function(interval) { + var weight = this.count - this.leftPoints; + if(interval[1] < this.mid) { + if(!this.left) { + return NOT_FOUND + } + var rw = this.right ? this.right.count : 0; + if(4 * rw > 3 * (weight-1)) { + return rebuildWithoutInterval(this, interval) + } + var r = this.left.remove(interval); + if(r === EMPTY) { + this.left = null; + this.count -= 1; + return SUCCESS + } else if(r === SUCCESS) { + this.count -= 1; + } + return r + } else if(interval[0] > this.mid) { + if(!this.right) { + return NOT_FOUND + } + var lw = this.left ? this.left.count : 0; + if(4 * lw > 3 * (weight-1)) { + return rebuildWithoutInterval(this, interval) + } + var r = this.right.remove(interval); + if(r === EMPTY) { + this.right = null; + this.count -= 1; + return SUCCESS + } else if(r === SUCCESS) { + this.count -= 1; + } + return r + } else { + if(this.count === 1) { + if(this.leftPoints[0] === interval) { + return EMPTY + } else { + return NOT_FOUND + } + } + if(this.leftPoints.length === 1 && this.leftPoints[0] === interval) { + if(this.left && this.right) { + var p = this; + var n = this.left; + while(n.right) { + p = n; + n = n.right; + } + if(p === this) { + n.right = this.right; + } else { + var l = this.left; + var r = this.right; + p.count -= n.count; + p.right = n.left; + n.left = l; + n.right = r; + } + copy(this, n); + this.count = (this.left?this.left.count:0) + (this.right?this.right.count:0) + this.leftPoints.length; + } else if(this.left) { + copy(this, this.left); + } else { + copy(this, this.right); + } + return SUCCESS + } + for(var l = bounds.ge(this.leftPoints, interval, compareBegin); l<this.leftPoints.length; ++l) { + if(this.leftPoints[l][0] !== interval[0]) { + break + } + if(this.leftPoints[l] === interval) { + this.count -= 1; + this.leftPoints.splice(l, 1); + for(var r = bounds.ge(this.rightPoints, interval, compareEnd); r<this.rightPoints.length; ++r) { + if(this.rightPoints[r][1] !== interval[1]) { + break + } else if(this.rightPoints[r] === interval) { + this.rightPoints.splice(r, 1); + return SUCCESS + } + } + } + } + return NOT_FOUND + } +}; + +function reportLeftRange(arr, hi, cb) { + for(var i=0; i<arr.length && arr[i][0] <= hi; ++i) { + var r = cb(arr[i]); + if(r) { return r } + } +} + +function reportRightRange(arr, lo, cb) { + for(var i=arr.length-1; i>=0 && arr[i][1] >= lo; --i) { + var r = cb(arr[i]); + if(r) { return r } + } +} + +function reportRange(arr, cb) { + for(var i=0; i<arr.length; ++i) { + var r = cb(arr[i]); + if(r) { return r } + } +} + +proto.queryPoint = function(x, cb) { + if(x < this.mid) { + if(this.left) { + var r = this.left.queryPoint(x, cb); + if(r) { return r } + } + return reportLeftRange(this.leftPoints, x, cb) + } else if(x > this.mid) { + if(this.right) { + var r = this.right.queryPoint(x, cb); + if(r) { return r } + } + return reportRightRange(this.rightPoints, x, cb) + } else { + return reportRange(this.leftPoints, cb) + } +}; + +proto.queryInterval = function(lo, hi, cb) { + if(lo < this.mid && this.left) { + var r = this.left.queryInterval(lo, hi, cb); + if(r) { return r } + } + if(hi > this.mid && this.right) { + var r = this.right.queryInterval(lo, hi, cb); + if(r) { return r } + } + if(hi < this.mid) { + return reportLeftRange(this.leftPoints, hi, cb) + } else if(lo > this.mid) { + return reportRightRange(this.rightPoints, lo, cb) + } else { + return reportRange(this.leftPoints, cb) + } +}; + +function compareNumbers(a, b) { + return a - b +} + +function compareBegin(a, b) { + var d = a[0] - b[0]; + if(d) { return d } + return a[1] - b[1] +} + +function compareEnd(a, b) { + var d = a[1] - b[1]; + if(d) { return d } + return a[0] - b[0] +} + +function createIntervalTree(intervals) { + if(intervals.length === 0) { + return null + } + var pts = []; + for(var i=0; i<intervals.length; ++i) { + pts.push(intervals[i][0], intervals[i][1]); + } + pts.sort(compareNumbers); + + var mid = pts[pts.length>>1]; + + var leftIntervals = []; + var rightIntervals = []; + var centerIntervals = []; + for(var i=0; i<intervals.length; ++i) { + var s = intervals[i]; + if(s[1] < mid) { + leftIntervals.push(s); + } else if(mid < s[0]) { + rightIntervals.push(s); + } else { + centerIntervals.push(s); + } + } + + //Split center intervals + var leftPoints = centerIntervals; + var rightPoints = centerIntervals.slice(); + leftPoints.sort(compareBegin); + rightPoints.sort(compareEnd); + + return new IntervalTreeNode(mid, + createIntervalTree(leftIntervals), + createIntervalTree(rightIntervals), + leftPoints, + rightPoints) +} + +//User friendly wrapper that makes it possible to support empty trees +function IntervalTree(root) { + this.root = root; +} + +var tproto = IntervalTree.prototype; + +tproto.insert = function(interval) { + if(this.root) { + this.root.insert(interval); + } else { + this.root = new IntervalTreeNode(interval[0], null, null, [interval], [interval]); + } +}; + +tproto.remove = function(interval) { + if(this.root) { + var r = this.root.remove(interval); + if(r === EMPTY) { + this.root = null; + } + return r !== NOT_FOUND + } + return false +}; + +tproto.queryPoint = function(p, cb) { + if(this.root) { + return this.root.queryPoint(p, cb) + } +}; + +tproto.queryInterval = function(lo, hi, cb) { + if(lo <= hi && this.root) { + return this.root.queryInterval(lo, hi, cb) + } +}; + +Object.defineProperty(tproto, "count", { + get: function() { + if(this.root) { + return this.root.count + } + return 0 + } +}); + +Object.defineProperty(tproto, "intervals", { + get: function() { + if(this.root) { + return this.root.intervals([]) + } + return [] + } +}); + +function createWrapper(intervals) { + if(!intervals || intervals.length === 0) { + return new IntervalTree(null) + } + return new IntervalTree(createIntervalTree(intervals)) +} + +var IntervalTree$1 = /*@__PURE__*/getDefaultExportFromCjs(intervalTree); + +const anchorXLeft = ({marginLeft}) => [1, marginLeft]; +const anchorXRight = ({width, marginRight}) => [-1, width - marginRight]; +const anchorXMiddle = ({width, marginLeft, marginRight}) => [0, (marginLeft + width - marginRight) / 2]; +const anchorYTop = ({marginTop}) => [1, marginTop]; +const anchorYBottom = ({height, marginBottom}) => [-1, height - marginBottom]; +const anchorYMiddle = ({height, marginTop, marginBottom}) => [0, (marginTop + height - marginBottom) / 2]; + +function maybeAnchor(anchor) { + return typeof anchor === "string" ? {anchor} : anchor; +} + +function dodgeX(dodgeOptions = {}, options = {}) { + if (arguments.length === 1) [dodgeOptions, options] = mergeOptions(dodgeOptions); + let {anchor = "left", padding = 1, r = options.r} = maybeAnchor(dodgeOptions); + switch (`${anchor}`.toLowerCase()) { + case "left": + anchor = anchorXLeft; + break; + case "right": + anchor = anchorXRight; + break; + case "middle": + anchor = anchorXMiddle; + break; + default: + throw new Error(`unknown dodge anchor: ${anchor}`); + } + return dodge("x", "y", anchor, number$1(padding), r, options); +} + +function dodgeY(dodgeOptions = {}, options = {}) { + if (arguments.length === 1) [dodgeOptions, options] = mergeOptions(dodgeOptions); + let {anchor = "bottom", padding = 1, r = options.r} = maybeAnchor(dodgeOptions); + switch (`${anchor}`.toLowerCase()) { + case "top": + anchor = anchorYTop; + break; + case "bottom": + anchor = anchorYBottom; + break; + case "middle": + anchor = anchorYMiddle; + break; + default: + throw new Error(`unknown dodge anchor: ${anchor}`); + } + return dodge("y", "x", anchor, number$1(padding), r, options); +} + +function mergeOptions(options) { + const {anchor, padding, ...rest} = options; + const {r} = rest; // don’t consume r; allow it to propagate + return [{anchor, padding, r}, rest]; +} + +function dodge(y, x, anchor, padding, r, options) { + if (r != null && typeof r !== "number") { + let {channels, sort, reverse} = options; + channels = maybeNamed(channels); + if (channels?.r === undefined) options = {...options, channels: {...channels, r: {value: r, scale: "r"}}}; + if (sort === undefined && reverse === undefined) options.sort = {channel: "-r"}; + } + return initializer(options, function (data, facets, channels, scales, dimensions, context) { + let {[x]: X, r: R} = channels; + if (!channels[x]) throw new Error(`missing channel: ${x}`); + ({[x]: X} = applyPosition(channels, scales, context)); + const cr = R ? undefined : r !== undefined ? number$1(r) : this.r !== undefined ? this.r : 3; + if (R) R = valueof(R.value, scales[R.scale] || identity$1, Float64Array); + let [ky, ty] = anchor(dimensions); + const compare = ky ? compareAscending : compareSymmetric; + const Y = new Float64Array(X.length); + const radius = R ? (i) => R[i] : () => cr; + for (let I of facets) { + const tree = IntervalTree$1(); + I = I.filter(R ? (i) => finite$1(X[i]) && positive(R[i]) : (i) => finite$1(X[i])); + const intervals = new Float64Array(2 * I.length + 2); + for (const i of I) { + const ri = radius(i); + const y0 = ky ? ri + padding : 0; // offset baseline for varying radius + const l = X[i] - ri; + const h = X[i] + ri; + + // The first two positions are 0 to test placing the dot on the baseline. + let k = 2; + + // For any previously placed circles that may overlap this circle, compute + // the y-positions that place this circle tangent to these other circles. + // https://observablehq.com/@mbostock/circle-offset-along-line + tree.queryInterval(l - padding, h + padding, ([, , j]) => { + const yj = Y[j] - y0; + const dx = X[i] - X[j]; + const dr = padding + (R ? R[i] + R[j] : 2 * cr); + const dy = Math.sqrt(dr * dr - dx * dx); + intervals[k++] = yj - dy; + intervals[k++] = yj + dy; + }); + + // Find the best y-value where this circle can fit. + let candidates = intervals.slice(0, k); + if (ky) candidates = candidates.filter((y) => y >= 0); + out: for (const y of candidates.sort(compare)) { + for (let j = 0; j < k; j += 2) { + if (intervals[j] + 1e-6 < y && y < intervals[j + 1] - 1e-6) { + continue out; + } + } + Y[i] = y + y0; + break; + } + + // Insert the placed circle into the interval tree. + tree.insert([l, h, i]); + } + } + if (!ky) ky = 1; + for (const I of facets) { + for (const i of I) { + Y[i] = Y[i] * ky + ty; + } + } + return { + data, + facets, + channels: { + [y]: {value: Y, source: null}, // don’t show in tooltip + [x]: {value: X, source: channels[x]}, + ...(R && {r: {value: R, source: channels.r}}) + } + }; + }); +} + +function compareSymmetric(a, b) { + return Math.abs(a) - Math.abs(b); +} + +function compareAscending(a, b) { + return a - b; +} + +function normalizeX(basis, options) { + if (arguments.length === 1) ({basis, ...options} = basis); + return mapX(normalize(basis), options); +} + +function normalizeY(basis, options) { + if (arguments.length === 1) ({basis, ...options} = basis); + return mapY(normalize(basis), options); +} + +function normalize(basis) { + if (basis === undefined) return normalizeFirst; + if (typeof basis === "function") return normalizeBasis(taker(basis)); + if (/^p\d{2}$/i.test(basis)) return normalizeAccessor(percentile(basis)); + switch (`${basis}`.toLowerCase()) { + case "deviation": + return normalizeDeviation; + case "first": + return normalizeFirst; + case "last": + return normalizeLast; + case "max": + return normalizeMax; + case "mean": + return normalizeMean; + case "median": + return normalizeMedian; + case "min": + return normalizeMin; + case "sum": + return normalizeSum; + case "extent": + return normalizeExtent; + } + throw new Error(`invalid basis: ${basis}`); +} + +function normalizeBasis(basis) { + return { + mapIndex(I, S, T) { + const b = +basis(I, S); + for (const i of I) { + T[i] = S[i] === null ? NaN : S[i] / b; + } + } + }; +} + +function normalizeAccessor(f) { + return normalizeBasis((I, S) => f(I, (i) => S[i])); +} + +const normalizeExtent = { + mapIndex(I, S, T) { + const [s1, s2] = d3.extent(I, (i) => S[i]); + const d = s2 - s1; + for (const i of I) { + T[i] = S[i] === null ? NaN : (S[i] - s1) / d; + } + } +}; + +const normalizeFirst = normalizeBasis((I, S) => { + for (let i = 0; i < I.length; ++i) { + const s = S[I[i]]; + if (defined(s)) return s; + } +}); + +const normalizeLast = normalizeBasis((I, S) => { + for (let i = I.length - 1; i >= 0; --i) { + const s = S[I[i]]; + if (defined(s)) return s; + } +}); + +const normalizeDeviation = { + mapIndex(I, S, T) { + const m = d3.mean(I, (i) => S[i]); + const d = d3.deviation(I, (i) => S[i]); + for (const i of I) { + T[i] = S[i] === null ? NaN : d ? (S[i] - m) / d : 0; + } + } +}; + +const normalizeMax = normalizeAccessor(d3.max); +const normalizeMean = normalizeAccessor(d3.mean); +const normalizeMedian = normalizeAccessor(d3.median); +const normalizeMin = normalizeAccessor(d3.min); +const normalizeSum = normalizeAccessor(d3.sum); + +function shiftX(interval, options) { + return shiftK("x", interval, options); +} + +function shiftK(x, interval, options = {}) { + let offset; + let k = 1; + if (typeof interval === "number") { + k = interval; + offset = (x, k) => +x + k; + } else { + if (typeof interval === "string") { + const sign = interval.startsWith("-") ? -1 : 1; + [interval, k] = parseTimeInterval(interval.replace(/^[+-]/, "")); + k *= sign; + } + interval = maybeInterval(interval); + offset = (x, k) => interval.offset(x, k); + } + const x1 = `${x}1`; + const x2 = `${x}2`; + const mapped = map( + { + [x1]: (D) => D.map((d) => offset(d, k)), + [x2]: (D) => D + }, + options + ); + const t = mapped[x2].transform; + mapped[x2].transform = () => { + const V = t(); + const [x0, x1] = d3.extent(V); + V.domain = k < 0 ? [x0, offset(x1, k)] : [offset(x0, k), x1]; + return V; + }; + return mapped; +} + +function select(selector, options = {}) { + // If specified selector is a string or function, it’s a selector without an + // input channel such as first or last. + if (typeof selector === "string") { + switch (selector.toLowerCase()) { + case "first": + return selectFirst(options); + case "last": + return selectLast(options); + } + } + if (typeof selector === "function") { + return selectChannel(null, selector, options); + } + // Otherwise the selector is an option {name: value} where name is a channel + // name and value is a selector definition that additionally takes the given + // channel values as input. The selector object must have exactly one key. + let key, value; + for (key in selector) { + if (value !== undefined) throw new Error("ambiguous selector; multiple inputs"); + value = maybeSelector(selector[key]); + } + if (value === undefined) throw new Error(`invalid selector: ${selector}`); + return selectChannel(key, value, options); +} + +function maybeSelector(selector) { + if (typeof selector === "function") return selector; + switch (`${selector}`.toLowerCase()) { + case "min": + return selectorMin; + case "max": + return selectorMax; + } + throw new Error(`unknown selector: ${selector}`); +} + +function selectFirst(options) { + return selectChannel(null, selectorFirst, options); +} + +function selectLast(options) { + return selectChannel(null, selectorLast, options); +} + +function selectMinX(options) { + return selectChannel("x", selectorMin, options); +} + +function selectMinY(options) { + return selectChannel("y", selectorMin, options); +} + +function selectMaxX(options) { + return selectChannel("x", selectorMax, options); +} + +function selectMaxY(options) { + return selectChannel("y", selectorMax, options); +} + +function* selectorFirst(I) { + yield I[0]; +} + +function* selectorLast(I) { + yield I[I.length - 1]; +} + +function* selectorMin(I, X) { + yield d3.least(I, (i) => X[i]); +} + +function* selectorMax(I, X) { + yield d3.greatest(I, (i) => X[i]); +} + +function selectChannel(v, selector, options) { + if (v != null) { + if (options[v] == null) throw new Error(`missing channel: ${v}`); + v = options[v]; + } + const z = maybeZ(options); + return basic(options, (data, facets) => { + const Z = valueof(data, z); + const V = valueof(data, v); + const selectFacets = []; + for (const facet of facets) { + const selectFacet = []; + for (const I of Z ? d3.group(facet, (i) => Z[i]).values() : [facet]) { + for (const i of selector(I, V)) { + selectFacet.push(i); + } + } + selectFacets.push(selectFacet); + } + return {data, facets: selectFacets}; + }); +} + +exports.Area = Area; +exports.Arrow = Arrow; +exports.BarX = BarX; +exports.BarY = BarY; +exports.Cell = Cell; +exports.Contour = Contour; +exports.Density = Density; +exports.Dot = Dot; +exports.Frame = Frame; +exports.Geo = Geo; +exports.Hexgrid = Hexgrid; +exports.Image = Image; +exports.Line = Line; +exports.Link = Link; +exports.Mark = Mark; +exports.Raster = Raster; +exports.Rect = Rect; +exports.RuleX = RuleX; +exports.RuleY = RuleY; +exports.Text = Text; +exports.TickX = TickX; +exports.TickY = TickY; +exports.Tip = Tip; +exports.Vector = Vector; +exports.area = area; +exports.areaX = areaX; +exports.areaY = areaY; +exports.arrow = arrow; +exports.auto = auto; +exports.autoSpec = autoSpec; +exports.axisFx = axisFx; +exports.axisFy = axisFy; +exports.axisX = axisX; +exports.axisY = axisY; +exports.barX = barX; +exports.barY = barY; +exports.bin = bin; +exports.binX = binX; +exports.binY = binY; +exports.bollinger = bollinger; +exports.bollingerX = bollingerX; +exports.bollingerY = bollingerY; +exports.boxX = boxX; +exports.boxY = boxY; +exports.cell = cell; +exports.cellX = cellX; +exports.cellY = cellY; +exports.centroid = centroid; +exports.circle = circle; +exports.cluster = cluster; +exports.column = column; +exports.contour = contour; +exports.crosshair = crosshair; +exports.crosshairX = crosshairX; +exports.crosshairY = crosshairY; +exports.delaunayLink = delaunayLink; +exports.delaunayMesh = delaunayMesh; +exports.density = density; +exports.differenceY = differenceY; +exports.dodgeX = dodgeX; +exports.dodgeY = dodgeY; +exports.dot = dot; +exports.dotX = dotX; +exports.dotY = dotY; +exports.filter = filter; +exports.find = find; +exports.formatIsoDate = formatIsoDate; +exports.formatMonth = formatMonth; +exports.formatWeekday = formatWeekday; +exports.frame = frame; +exports.geo = geo; +exports.geoCentroid = geoCentroid; +exports.graticule = graticule; +exports.gridFx = gridFx; +exports.gridFy = gridFy; +exports.gridX = gridX; +exports.gridY = gridY; +exports.group = group; +exports.groupX = groupX; +exports.groupY = groupY; +exports.groupZ = groupZ$1; +exports.hexagon = hexagon; +exports.hexbin = hexbin; +exports.hexgrid = hexgrid; +exports.hull = hull; +exports.identity = identity$1; +exports.image = image; +exports.indexOf = indexOf; +exports.initializer = initializer; +exports.interpolateNearest = interpolateNearest; +exports.interpolateNone = interpolateNone; +exports.interpolatorBarycentric = interpolatorBarycentric; +exports.interpolatorRandomWalk = interpolatorRandomWalk; +exports.legend = legend; +exports.line = line; +exports.lineX = lineX; +exports.lineY = lineY; +exports.linearRegressionX = linearRegressionX; +exports.linearRegressionY = linearRegressionY; +exports.link = link; +exports.map = map; +exports.mapX = mapX; +exports.mapY = mapY; +exports.marks = marks; +exports.normalize = normalize; +exports.normalizeX = normalizeX; +exports.normalizeY = normalizeY; +exports.plot = plot; +exports.pointer = pointer; +exports.pointerX = pointerX; +exports.pointerY = pointerY; +exports.raster = raster; +exports.rect = rect; +exports.rectX = rectX; +exports.rectY = rectY; +exports.reverse = reverse; +exports.ruleX = ruleX; +exports.ruleY = ruleY; +exports.scale = scale; +exports.select = select; +exports.selectFirst = selectFirst; +exports.selectLast = selectLast; +exports.selectMaxX = selectMaxX; +exports.selectMaxY = selectMaxY; +exports.selectMinX = selectMinX; +exports.selectMinY = selectMinY; +exports.shiftX = shiftX; +exports.shuffle = shuffle; +exports.sort = sort; +exports.sphere = sphere; +exports.spike = spike; +exports.stackX = stackX; +exports.stackX1 = stackX1; +exports.stackX2 = stackX2; +exports.stackY = stackY; +exports.stackY1 = stackY1; +exports.stackY2 = stackY2; +exports.text = text; +exports.textX = textX; +exports.textY = textY; +exports.tickX = tickX; +exports.tickY = tickY; +exports.tip = tip; +exports.transform = basic; +exports.tree = tree; +exports.treeLink = treeLink; +exports.treeNode = treeNode; +exports.valueof = valueof; +exports.vector = vector; +exports.vectorX = vectorX; +exports.vectorY = vectorY; +exports.version = version; +exports.voronoi = voronoi; +exports.voronoiMesh = voronoiMesh; +exports.window = window$1; +exports.windowX = windowX; +exports.windowY = windowY; + +})); diff --git a/snag/templates/dashboard/dashboard.html b/snag/templates/dashboard/dashboard.html index c061b66..668c1bc 100644 --- a/snag/templates/dashboard/dashboard.html +++ b/snag/templates/dashboard/dashboard.html @@ -3,37 +3,124 @@ {% block headline %}{% block title %}Dashboard{% endblock %}{% endblock %} {% block content %} +<script src="static/d3.js"></script> +<script src="static/plot.js"></script> +<script> + async function fetchDeviceData(url = "", payload = {}) { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(payload) + }); + return response.json(); + } + + const deviceIdMap = new Map(); + {% for entry in deviceList %} + deviceIdMap.set({{ entry.deviceId }}, "{{ entry.name }}"); + {% endfor %} +</script> + <main> {% if deviceList|length == 0 %} Hmmm nothing seems to be logged yet {% endif %} + + <div id="graphAllIndoor" class="graph"></div> + <script type="module"> + const url = "/data/device"; + const payload = { + "devices": [ {% for entry in deviceList %} "{{ entry.deviceId }}", {% endfor %} ], + "environment": "indoor", + "measurements": ["temperature"] + }; + var data = await fetchDeviceData(url, payload); + const div = document.querySelector("#graphAllIndoor"); + + const plot = Plot.plot({ + title: "Indoor Temperatures", + subtitle: "Showing the last 24 hours", + y: { + grid: true, + label: "Temperature (F)" + }, + x: {label: "Timestamp"}, + color: {legend: true}, + width: div.getBoundingClientRect().width, + marks: [ + Plot.frame(), + Plot.lineY(data, { + x: (d) => new Date(d.timeStamp), + y: (d) => d.temperature * 9 / 5 + 32, + stroke: (d) => deviceIdMap.get(d.deviceId), + tip: true + }) + ] + }); + + div.append(plot); + </script> + {% for entry in deviceList %} <h2> {{ entry.name }} | {{ entry.location }} </h2> - <table> - <tr> - {% if entry.location != "status" %} - <td> - <figure> - <embed type="image/png" src="/graph/humidity-temperature.png?deviceId={{ entry.deviceId }}&type={{ entry.location}}" /> - </figure> - </td> - - <td> - <figure> - <embed type="image/png" src="/graph/pressure.png?deviceId={{ entry.deviceId }}&type={{ entry.location}}" /> - </figure> - </td> - {% else %} - <td> - <figure> - <embed type="image/png" src="/graph/battery.png?deviceId={{ entry.deviceId }}" /> - </figure> - </td> - {% endif %} - </tr> + <h3> {{ entry.environment }} | {{ entry.environmentDesc }} </h3> + <div id="graph{{ entry.deviceId }}" class="graph"> + <figure id="graphTemperature{{ entry.deviceId }}"></figure> + <figure id="graphHumidity{{ entry.deviceId }}"></figure> + </div> + <script type="module"> + const url = "/data/device/{{ entry.deviceId }}"; + const payload = { + "environment": "{{ entry.environment }}", + "measurements": ["temperature", "humidity"] + }; + var data = await fetchDeviceData(url, payload); - </table> + const figTemp = document.querySelector("#graphTemperature{{ entry.deviceId }}"); + const plotTemp = Plot.plot({ + y: { + axis: "left", + grid: true, + label: "Temperature (F)" + }, + x: {label: "Timestamp"}, + width: figTemp.getBoundingClientRect().width, + marks: [ + Plot.frame(), + Plot.lineY(data, { + x: (d) => new Date(d.timeStamp), + y: (d) => d.temperature * 9 / 5 + 32, + stroke: "orange", + tip: true + }) + ] + }); + figTemp.append(plotTemp); + + const figHumid = document.querySelector("#graphHumidity{{ entry.deviceId }}"); + const plotHumid = Plot.plot({ + y: { + axis: "left", + grid: true, + label: "Humidity (%)" + }, + x: {label: "Timestamp"}, + width: figHumid.getBoundingClientRect().width, + marks: [ + Plot.frame(), + Plot.lineY(data, { + x: (d) => new Date(d.timeStamp), + y: "humidity", + stroke: "steelBlue", + tip: true + }) + ] + }); + figHumid.append(plotHumid); + </script> {% endfor %} |