Repository: d3/d3-transition Branch: main Commit: 7a3b625d8613 Files: 72 Total size: 124.7 KB Directory structure: gitextract_uicvwejc/ ├── .eslintrc.json ├── .github/ │ ├── eslint.json │ └── workflows/ │ └── node.js.yml ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── rollup.config.js ├── src/ │ ├── active.js │ ├── index.js │ ├── interrupt.js │ ├── selection/ │ │ ├── index.js │ │ ├── interrupt.js │ │ └── transition.js │ └── transition/ │ ├── attr.js │ ├── attrTween.js │ ├── delay.js │ ├── duration.js │ ├── ease.js │ ├── easeVarying.js │ ├── end.js │ ├── filter.js │ ├── index.js │ ├── interpolate.js │ ├── merge.js │ ├── on.js │ ├── remove.js │ ├── schedule.js │ ├── select.js │ ├── selectAll.js │ ├── selection.js │ ├── style.js │ ├── styleTween.js │ ├── text.js │ ├── textTween.js │ ├── transition.js │ └── tween.js └── test/ ├── .eslintrc.json ├── active-test.js ├── error-test.js ├── interrupt-test.js ├── jsdom.js ├── selection/ │ ├── interrupt-test.js │ └── transition-test.js └── transition/ ├── attr-test.js ├── attrTween-test.js ├── call-test.js ├── delay-test.js ├── duration-test.js ├── each-test.js ├── ease-test.js ├── easeVarying-test.js ├── empty-test.js ├── filter-test.js ├── index-test.js ├── merge-test.js ├── node-test.js ├── nodes-test.js ├── on-test.js ├── remove-test.js ├── select-test.js ├── selectAll-test.js ├── selectChild-test.js ├── selectChildren-test.js ├── selection-test.js ├── size-test.js ├── style-test.js ├── styleTween-test.js ├── text-test.js ├── textTween-test.js ├── transition-test.js └── tween-test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "extends": "eslint:recommended", "parserOptions": { "sourceType": "module", "ecmaVersion": 8 }, "env": { "es6": true }, "rules": { "no-cond-assign": 0 } } ================================================ FILE: .github/eslint.json ================================================ { "problemMatcher": [ { "owner": "eslint-compact", "pattern": [ { "regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s(Error|Warning|Info)\\s-\\s(.+)\\s\\((.+)\\)$", "file": 1, "line": 2, "column": 3, "severity": 4, "message": 5, "code": 6 } ] } ] } ================================================ FILE: .github/workflows/node.js.yml ================================================ # https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Node.js CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: yarn --frozen-lockfile - run: | echo ::add-matcher::.github/eslint.json yarn run eslint src test --format=compact - run: yarn test ================================================ FILE: .gitignore ================================================ *.sublime-workspace .DS_Store dist/ node_modules npm-debug.log ================================================ FILE: LICENSE ================================================ Copyright 2010-2021 Mike Bostock Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================ FILE: README.md ================================================ # d3-transition A transition is a [selection](https://github.com/d3/d3-selection)-like interface for animating changes to the DOM. Instead of applying changes instantaneously, transitions smoothly interpolate the DOM from its current state to the desired target state over a given duration. ## Resources - [Documentation](https://d3js.org/d3-transition) - [Examples](https://observablehq.com/collection/@d3/d3-transition) - [Releases](https://github.com/d3/d3-transition/releases) - [Getting help](https://d3js.org/community) ================================================ FILE: package.json ================================================ { "name": "d3-transition", "version": "3.0.1", "description": "Animated transitions for D3 selections.", "homepage": "https://d3js.org/d3-transition/", "repository": { "type": "git", "url": "https://github.com/d3/d3-transition.git" }, "keywords": [ "d3", "d3-module", "dom", "transition", "animation" ], "license": "ISC", "author": { "name": "Mike Bostock", "url": "https://bost.ocks.org/mike" }, "type": "module", "files": [ "dist/**/*.js", "src/**/*.js" ], "module": "src/index.js", "main": "src/index.js", "jsdelivr": "dist/d3-transition.min.js", "unpkg": "dist/d3-transition.min.js", "exports": { "umd": "./dist/d3-transition.min.js", "default": "./src/index.js" }, "sideEffects": [ "./src/index.js", "./src/selection/index.js" ], "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "devDependencies": { "d3-selection": "2 - 3", "eslint": "7", "jsdom": "16", "mocha": "9", "rollup": "2", "rollup-plugin-terser": "7" }, "scripts": { "test": "mocha 'test/**/*-test.js' && eslint src test", "prepublishOnly": "rm -rf dist && yarn test && rollup -c && git push", "postpublish": "git push --tags && cd ../d3.github.com && git pull && cp ../${npm_package_name}/dist/${npm_package_name}.js ${npm_package_name}.v${npm_package_version%%.*}.js && cp ../${npm_package_name}/dist/${npm_package_name}.min.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git add ${npm_package_name}.v${npm_package_version%%.*}.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git commit -m \"${npm_package_name} ${npm_package_version}\" && git push && cd -" }, "engines": { "node": ">=12" }, "peerDependencies": { "d3-selection": "2 - 3" } } ================================================ FILE: rollup.config.js ================================================ import {readFileSync} from "fs"; import {terser} from "rollup-plugin-terser"; import * as meta from "./package.json"; // Extract copyrights from the LICENSE. const copyright = readFileSync("./LICENSE", "utf-8") .split(/\n/g) .filter(line => /^Copyright\s+/.test(line)) .map(line => line.replace(/^Copyright\s+/, "")) .join(", "); const config = { input: "src/index.js", external: Object.keys({...meta.dependencies, ...meta.peerDependencies}).filter(key => /^d3-/.test(key)), output: { file: `dist/${meta.name}.js`, name: "d3", format: "umd", indent: false, extend: true, banner: `// ${meta.homepage} v${meta.version} Copyright ${copyright}`, globals: Object.assign({}, ...Object.keys({...meta.dependencies, ...meta.peerDependencies}).filter(key => /^d3-/.test(key)).map(key => ({[key]: "d3"}))) }, plugins: [], onwarn(message, warn) { if (message.code === "CIRCULAR_DEPENDENCY") return; warn(message); } }; export default [ config, { ...config, output: { ...config.output, file: `dist/${meta.name}.min.js` }, plugins: [ ...config.plugins, terser({ output: { preamble: config.output.banner } }) ] } ]; ================================================ FILE: src/active.js ================================================ import {Transition} from "./transition/index.js"; import {SCHEDULED} from "./transition/schedule.js"; var root = [null]; export default function(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; } ================================================ FILE: src/index.js ================================================ import "./selection/index.js"; export {default as transition} from "./transition/index.js"; export {default as active} from "./active.js"; export {default as interrupt} from "./interrupt.js"; ================================================ FILE: src/interrupt.js ================================================ import {STARTING, ENDING, ENDED} from "./transition/schedule.js"; export default function(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; } ================================================ FILE: src/selection/index.js ================================================ import {selection} from "d3-selection"; import selection_interrupt from "./interrupt.js"; import selection_transition from "./transition.js"; selection.prototype.interrupt = selection_interrupt; selection.prototype.transition = selection_transition; ================================================ FILE: src/selection/interrupt.js ================================================ import interrupt from "../interrupt.js"; export default function(name) { return this.each(function() { interrupt(this, name); }); } ================================================ FILE: src/selection/transition.js ================================================ import {Transition, newId} from "../transition/index.js"; import schedule from "../transition/schedule.js"; import {easeCubicInOut} from "d3-ease"; import {now} from "d3-timer"; var defaultTiming = { time: null, // Set on use. delay: 0, duration: 250, ease: easeCubicInOut }; 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; } export default function(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); } ================================================ FILE: src/transition/attr.js ================================================ import {interpolateTransformSvg as interpolateTransform} from "d3-interpolate"; import {namespace} from "d3-selection"; import {tweenValue} from "./tween.js"; import interpolate from "./interpolate.js"; 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)); }; } export default function(name, value) { var fullname = namespace(name), i = fullname === "transform" ? interpolateTransform : interpolate; 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)); } ================================================ FILE: src/transition/attrTween.js ================================================ import {namespace} from "d3-selection"; 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; } export default function(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)); } ================================================ FILE: src/transition/delay.js ================================================ import {get, init} from "./schedule.js"; 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; }; } export default function(value) { var id = this._id; return arguments.length ? this.each((typeof value === "function" ? delayFunction : delayConstant)(id, value)) : get(this.node(), id).delay; } ================================================ FILE: src/transition/duration.js ================================================ import {get, set} from "./schedule.js"; 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; }; } export default function(value) { var id = this._id; return arguments.length ? this.each((typeof value === "function" ? durationFunction : durationConstant)(id, value)) : get(this.node(), id).duration; } ================================================ FILE: src/transition/ease.js ================================================ import {get, set} from "./schedule.js"; function easeConstant(id, value) { if (typeof value !== "function") throw new Error; return function() { set(this, id).ease = value; }; } export default function(value) { var id = this._id; return arguments.length ? this.each(easeConstant(id, value)) : get(this.node(), id).ease; } ================================================ FILE: src/transition/easeVarying.js ================================================ import {set} from "./schedule.js"; function easeVarying(id, value) { return function() { var v = value.apply(this, arguments); if (typeof v !== "function") throw new Error; set(this, id).ease = v; }; } export default function(value) { if (typeof value !== "function") throw new Error; return this.each(easeVarying(this._id, value)); } ================================================ FILE: src/transition/end.js ================================================ import {set} from "./schedule.js"; export default function() { 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(); }); } ================================================ FILE: src/transition/filter.js ================================================ import {matcher} from "d3-selection"; import {Transition} from "./index.js"; export default function(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); } ================================================ FILE: src/transition/index.js ================================================ import {selection} from "d3-selection"; import transition_attr from "./attr.js"; import transition_attrTween from "./attrTween.js"; import transition_delay from "./delay.js"; import transition_duration from "./duration.js"; import transition_ease from "./ease.js"; import transition_easeVarying from "./easeVarying.js"; import transition_filter from "./filter.js"; import transition_merge from "./merge.js"; import transition_on from "./on.js"; import transition_remove from "./remove.js"; import transition_select from "./select.js"; import transition_selectAll from "./selectAll.js"; import transition_selection from "./selection.js"; import transition_style from "./style.js"; import transition_styleTween from "./styleTween.js"; import transition_text from "./text.js"; import transition_textTween from "./textTween.js"; import transition_transition from "./transition.js"; import transition_tween from "./tween.js"; import transition_end from "./end.js"; var id = 0; export function Transition(groups, parents, name, id) { this._groups = groups; this._parents = parents; this._name = name; this._id = id; } export default function transition(name) { return selection().transition(name); } export 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] }; ================================================ FILE: src/transition/interpolate.js ================================================ import {color} from "d3-color"; import {interpolateNumber, interpolateRgb, interpolateString} from "d3-interpolate"; export default function(a, b) { var c; return (typeof b === "number" ? interpolateNumber : b instanceof color ? interpolateRgb : (c = color(b)) ? (b = c, interpolateRgb) : interpolateString)(a, b); } ================================================ FILE: src/transition/merge.js ================================================ import {Transition} from "./index.js"; export default function(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); } ================================================ FILE: src/transition/on.js ================================================ import {get, set, init} from "./schedule.js"; 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; }; } export default function(name, listener) { var id = this._id; return arguments.length < 2 ? get(this.node(), id).on.on(name) : this.each(onFunction(id, name, listener)); } ================================================ FILE: src/transition/remove.js ================================================ function removeFunction(id) { return function() { var parent = this.parentNode; for (var i in this.__transition) if (+i !== id) return; if (parent) parent.removeChild(this); }; } export default function() { return this.on("end.remove", removeFunction(this._id)); } ================================================ FILE: src/transition/schedule.js ================================================ import {dispatch} from "d3-dispatch"; import {timer, timeout} from "d3-timer"; var emptyOn = dispatch("start", "end", "cancel", "interrupt"); var emptyTween = []; export var CREATED = 0; export var SCHEDULED = 1; export var STARTING = 2; export var STARTED = 3; export var RUNNING = 4; export var ENDING = 5; export var ENDED = 6; export default function(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 }); } export function init(node, id) { var schedule = get(node, id); if (schedule.state > CREATED) throw new Error("too late; already scheduled"); return schedule; } export function set(node, id) { var schedule = get(node, id); if (schedule.state > STARTED) throw new Error("too late; already running"); return schedule; } export 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; } } ================================================ FILE: src/transition/select.js ================================================ import {selector} from "d3-selection"; import {Transition} from "./index.js"; import schedule, {get} from "./schedule.js"; export default function(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); } ================================================ FILE: src/transition/selectAll.js ================================================ import {selectorAll} from "d3-selection"; import {Transition} from "./index.js"; import schedule, {get} from "./schedule.js"; export default function(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); } ================================================ FILE: src/transition/selection.js ================================================ import {selection} from "d3-selection"; var Selection = selection.prototype.constructor; export default function() { return new Selection(this._groups, this._parents); } ================================================ FILE: src/transition/style.js ================================================ import {interpolateTransformCss as interpolateTransform} from "d3-interpolate"; import {style} from "d3-selection"; import {set} from "./schedule.js"; import {tweenValue} from "./tween.js"; import interpolate from "./interpolate.js"; function styleNull(name, interpolate) { var string00, string10, interpolate0; return function() { var string0 = style(this, name), string1 = (this.style.removeProperty(name), style(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 = style(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 = style(this, name), value1 = value(this), string1 = value1 + ""; if (value1 == null) string1 = value1 = (this.style.removeProperty(name), style(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; }; } export default function(name, value, priority) { var i = (name += "") === "transform" ? interpolateTransform : interpolate; 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); } ================================================ FILE: src/transition/styleTween.js ================================================ 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; } export default function(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)); } ================================================ FILE: src/transition/text.js ================================================ import {tweenValue} from "./tween.js"; function textConstant(value) { return function() { this.textContent = value; }; } function textFunction(value) { return function() { var value1 = value(this); this.textContent = value1 == null ? "" : value1; }; } export default function(value) { return this.tween("text", typeof value === "function" ? textFunction(tweenValue(this, "text", value)) : textConstant(value == null ? "" : value + "")); } ================================================ FILE: src/transition/textTween.js ================================================ 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; } export default function(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)); } ================================================ FILE: src/transition/transition.js ================================================ import {Transition, newId} from "./index.js"; import schedule, {get} from "./schedule.js"; export default function() { 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); } ================================================ FILE: src/transition/tween.js ================================================ import {get, set} from "./schedule.js"; 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; }; } export default function(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)); } export 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]; }; } ================================================ FILE: test/.eslintrc.json ================================================ { "extends": "eslint:recommended", "parserOptions": { "sourceType": "module", "ecmaVersion": 8 }, "env": { "mocha": true, "node": true, "es6": true, "browser": true } } ================================================ FILE: test/active-test.js ================================================ import assert from "assert"; import {select} from "d3-selection"; import {timeout} from "d3-timer"; import {active} from "../src/index.js"; import it from "./jsdom.js"; it("active(node) returns null if the specified node has no active transition with the null name", async () => { const root = document.documentElement; const s = select(root); // No transitions pending. assert.strictEqual(active(root), null); // Two transitions created. s.transition().delay(50).duration(50); s.transition("foo").duration(50); assert.strictEqual(active(root), null); // One transition scheduled; one active with a different name. await new Promise(resolve => timeout(() => { assert.strictEqual(active(root), null); resolve(); })); // No transitions remaining after the transition ends. await new Promise(resolve => timeout(() => { assert.strictEqual(active(root), null); resolve(); }, 100)); }); it("active(node, null) returns null if the specified node has no active transition with the null name", async () => { const root = document.documentElement; const s = select(root); // No transitions pending. assert.strictEqual(active(root, null), null); // Two transitions created. s.transition().delay(50).duration(50); s.transition("foo").duration(50); assert.strictEqual(active(root, null), null); // One transition scheduled; one active with a different name. await new Promise(resolve => timeout(() => { assert.strictEqual(active(root, null), null); resolve(); })); // No transitions remaining after the transition ends. await new Promise(resolve => timeout(() => { assert.strictEqual(active(root, null), null); resolve(); }, 100)); }); it("active(node, undefined) returns null if the specified node has no active transition with the null name", async () => { const root = document.documentElement; const s = select(root); // No transitions pending. assert.strictEqual(active(root, undefined), null); // Two transitions created. s.transition().delay(50).duration(50); s.transition("foo").duration(50); assert.strictEqual(active(root, undefined), null); // One transition scheduled; one active with a different name. await new Promise(resolve => timeout(() => { assert.strictEqual(active(root, undefined), null); resolve(); })); // No transitions remaining after the transition ends. await new Promise(resolve => timeout(() => { assert.strictEqual(active(root, undefined), null); resolve(); }, 100)); }); it("active(node, name) returns null if the specified node has no active transition with the specified name", async () => { const root = document.documentElement; const s = select(root); // No transitions pending. assert.strictEqual(active(root, "foo"), null); // Two transitions created. s.transition("foo").delay(50).duration(50); s.transition().duration(50); assert.strictEqual(active(root, null), null); // One transition scheduled; one active with a different name. assert.strictEqual(active(root, "foo"), null); // One transition scheduled. await new Promise(resolve => timeout(() => { assert.strictEqual(active(root, "foo"), null); resolve(); })); // No transitions remaining after the transition ends. await new Promise(resolve => timeout(() => { assert.strictEqual(active(root, "foo"), null); resolve(); }, 100)); }); it("active(node) returns the active transition on the specified node with the null name", async () => { const root = document.documentElement; const s = select(root); const t = s.transition().on("start", check).tween("tween", tweened).on("end", check); function check() { const a = active(root); assert.deepStrictEqual(a._groups, [[root]]); assert.deepStrictEqual(a._parents, [null]); assert.strictEqual(a._name, null); assert.strictEqual(a._id, t._id); } function tweened() { check(); return t => { if (t >= 1) check(); }; } await t.end(); }); it("active(node, name) returns the active transition on the specified node with the specified name", async () => { const root = document.documentElement; const s = select(root); const t = s.transition("foo").on("start", check).tween("tween", tweened).on("end", check); function check() { const a = active(root, "foo"); assert.deepStrictEqual(a._groups, [[root]]); assert.deepStrictEqual(a._parents, [null]); assert.strictEqual(a._name, "foo"); assert.strictEqual(a._id, t._id); } function tweened() { check(); return t => { if (t >= 1) check(); }; } await t.end(); }); ================================================ FILE: test/error-test.js ================================================ import assert from "assert"; import {select} from "d3-selection"; import {timeout} from "d3-timer"; import "../src/index.js"; import it from "./jsdom.js"; describe("with an uncaught error", () => { let listeners; beforeEach(() => { listeners = process.listeners("uncaughtException"); process.removeAllListeners("uncaughtException"); process.once("uncaughtException", () => {}); }); afterEach(() => { for (const listener of listeners) { process.on("uncaughtException", listener); } }); it("transition.on(\"start\", error) terminates the transition", async () => { const root = document.documentElement; const s = select(root); s.transition().on("start", () => { throw new Error; }); await new Promise(resolve => timeout(resolve)); assert.strictEqual(root.__transition, undefined); }); it("transition.on(\"start\", error) with delay terminates the transition", async () => { const root = document.documentElement; const s = select(root); s.transition().delay(50).on("start", () => { throw new Error; }); await new Promise(resolve => timeout(resolve, 50)); assert.strictEqual(root.__transition, undefined); }); it("transition.tween(\"foo\", error) terminates the transition", async () => { const root = document.documentElement; const s = select(root); s.transition().tween("foo", () => { throw new Error; }); await new Promise(resolve => timeout(resolve)); assert.strictEqual(root.__transition, undefined); }); it("transition.tween(\"foo\", error) with delay terminates the transition", async () => { const root = document.documentElement; const s = select(root); s.transition().delay(50).tween("foo", () => { throw new Error; }); await new Promise(resolve => timeout(resolve, 50)); assert.strictEqual(root.__transition, undefined); }); it("transition.tween(\"foo\", deferredError) terminates the transition", async () => { const root = document.documentElement; const s = select(root); s.transition().duration(50).tween("foo", () => { return function(t) { if (t === 1) throw new Error; }; }); await new Promise(resolve => timeout(resolve, 50)); assert.strictEqual(root.__transition, undefined); }); it("transition.on(\"end\", error) terminates the transition", async () => { const root = document.documentElement; const s = select(root); s.transition().delay(50).duration(50).on("end", () => { throw new Error; }); await new Promise(resolve => timeout(resolve, 100)); assert.strictEqual(root.__transition, undefined); }); }); ================================================ FILE: test/interrupt-test.js ================================================ import assert from "assert"; import {select} from "d3-selection"; import {interrupt} from "../src/index.js"; import it from "./jsdom.js"; it("interrupt(node) cancels any pending transitions on the specified node", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition(); const t2 = t1.transition(); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, true); interrupt(root); assert.strictEqual(root.__transition, undefined); }); it("selection.interrupt(name) only cancels pending transitions with the specified name", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition("foo"); const t2 = s.transition(); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, true); interrupt(root, "foo"); assert.strictEqual(t1._id in root.__transition, false); assert.strictEqual(t2._id in root.__transition, true); }); ================================================ FILE: test/jsdom.js ================================================ import {JSDOM} from "jsdom"; export default function jsdomit(message, html, run) { if (arguments.length < 3) run = html, html = ""; return it(message, async () => { try { const dom = new JSDOM(html); global.window = dom.window; global.document = dom.window.document; await run(); } finally { delete global.window; delete global.document; } }); } ================================================ FILE: test/selection/interrupt-test.js ================================================ import assert from "assert"; import {select} from "d3-selection"; import {timeout} from "d3-timer"; import "../../src/index.js"; import {CREATED, ENDED, ENDING, SCHEDULED, STARTED, STARTING} from "../../src/transition/schedule.js"; import it from "../jsdom.js"; it("selection.interrupt() returns the selection", () => { const s = select(document); assert.strictEqual(s.interrupt(), s); }); it("selection.interrupt() cancels any pending transitions on the selected elements", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition(); const t2 = t1.transition(); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, true); assert.strictEqual(s.interrupt(), s); assert.strictEqual(root.__transition, undefined); }); it("selection.interrupt() only cancels pending transitions with the null name", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition("foo"); const t2 = s.transition(); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, true); assert.strictEqual(s.interrupt(), s); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, false); }); it("selection.interrupt(null) only cancels pending transitions with the null name", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition("foo"); const t2 = s.transition(); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, true); assert.strictEqual(s.interrupt(null), s); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, false); }); it("selection.interrupt(undefined) only cancels pending transitions with the null name", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition("foo"); const t2 = s.transition(); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, true); assert.strictEqual(s.interrupt(undefined), s); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, false); }); it("selection.interrupt(name) only cancels pending transitions with the specified name", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition("foo"); const t2 = s.transition(); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, true); assert.strictEqual(s.interrupt("foo"), s); assert.strictEqual(t1._id in root.__transition, false); assert.strictEqual(t2._id in root.__transition, true); }); it("selection.interrupt(name) coerces the name to a string", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition("foo"); const t2 = s.transition(); assert.strictEqual(t1._id in root.__transition, true); assert.strictEqual(t2._id in root.__transition, true); assert.strictEqual(s.interrupt({toString() { return "foo"; }}), s); assert.strictEqual(t1._id in root.__transition, false); assert.strictEqual(t2._id in root.__transition, true); }); it("selection.interrupt() does nothing if there is no transition on the selected elements", () => { const root = document.documentElement; const s = select(root); assert.strictEqual(root.__transition, undefined); assert.strictEqual(s.interrupt(), s); assert.strictEqual(root.__transition, undefined); }); it("selection.interrupt() dispatches an interrupt event to the started transition on the selected elements", async () => { const root = document.documentElement; let interrupts = 0; const s = select(root); const t = s.transition().on("interrupt", () => { ++interrupts; }); await new Promise(resolve => timeout(() => { const schedule = root.__transition[t._id]; assert.strictEqual(schedule.state, STARTED); s.interrupt(); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(root.__transition, undefined); assert.strictEqual(interrupts, 1); resolve(); })); }); it("selection.interrupt() destroys the schedule after dispatching the interrupt event", async () => { const root = document.documentElement; const s = select(root); const t = s.transition().on("interrupt", interrupted); await new Promise(resolve => timeout(() => { s.interrupt(); resolve(); })); function interrupted() { assert.strictEqual(t.delay(), 0); assert.strictEqual(t.duration(), 250); assert.strictEqual(t.on("interrupt"), interrupted); } }); it("selection.interrupt() does not dispatch an interrupt event to a starting transition", async () => { const root = document.documentElement; let interrupts = 0; const s = select(root); const t = s.transition().on("interrupt", () => { ++interrupts; }); await new Promise(resolve => t.on("start", () => { const schedule = root.__transition[t._id]; assert.strictEqual(schedule.state, STARTING); s.interrupt(); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(root.__transition, undefined); assert.strictEqual(interrupts, 0); resolve(); })); }); it("selection.interrupt() prevents a created transition from starting", async () => { const root = document.documentElement; let starts = 0; const s = select(root); const t = s.transition().on("start", () => { ++starts; }); const schedule = root.__transition[t._id]; assert.strictEqual(schedule.state, CREATED); s.interrupt(); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(root.__transition, undefined); await new Promise(resolve => timeout(resolve, 10)); assert.strictEqual(starts, 0); }); it("selection.interrupt() prevents a scheduled transition from starting", async () => { const root = document.documentElement; let starts = 0; const s = select(root); const t = s.transition().delay(50).on("start", () => { ++starts; }); const schedule = root.__transition[t._id]; await new Promise(resolve => timeout(resolve)); assert.strictEqual(schedule.state, SCHEDULED); s.interrupt(); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(root.__transition, undefined); await new Promise(resolve => timeout(resolve, 60)); assert.strictEqual(starts, 0); }); it("selection.interrupt() prevents a starting transition from initializing tweens", async () => { const root = document.documentElement; let tweens = 0; const s = select(root); const t = s.transition().tween("tween", () => { ++tweens; }); const schedule = root.__transition[t._id]; await new Promise(resolve => t.on("start", () => { assert.strictEqual(schedule.state, STARTING); s.interrupt(); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(root.__transition, undefined); resolve(); })); await new Promise(resolve => timeout(resolve, 10)); assert.strictEqual(tweens, 0); }); it("selection.interrupt() during tween initialization prevents an active transition from continuing", async () => { const root = document.documentElement; let tweens = 0; const s = select(root); const t = s.transition().tween("tween", () => { s.interrupt(); return () => { ++tweens; }; }); const schedule = root.__transition[t._id]; await new Promise(resolve => timeout(resolve, 10)); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(root.__transition, undefined); assert.strictEqual(tweens, 0); }); it("selection.interrupt() prevents an active transition from continuing", async () => { const root = document.documentElement; let interrupted = false; let tweens = 0; const s = select(root); const t = s.transition().tween("tween", () => () => { if (interrupted) ++tweens; }); const schedule = root.__transition[t._id]; await new Promise(resolve => timeout(() => { interrupted = true; assert.strictEqual(schedule.state, STARTED); s.interrupt(); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(root.__transition, undefined); resolve(); }, 10)); await new Promise(resolve => timeout(resolve, 50)); assert.strictEqual(tweens, 0); }); it("selection.interrupt() during the final tween invocation prevents the end event from being dispatched", async () => { const root = document.documentElement; let ends = 0; const s = select(root); const t = s.transition().duration(50).tween("tween", tween).on("end", () => { ++ends; }); const schedule = root.__transition[t._id]; function tween() { return (t) => { if (t >= 1) { assert.strictEqual(schedule.state, ENDING); s.interrupt(); } }; } await new Promise(resolve => timeout(() => { assert.strictEqual(schedule.timer._call, null); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(root.__transition, undefined); assert.strictEqual(ends, 0); resolve(); }, 60)); }); it("selection.interrupt() has no effect on an ended transition", async () => { const root = document.documentElement; const s = select(root); const t = s.transition().duration(50); const schedule = root.__transition[t._id]; await t.end(); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(schedule.timer._call, null); s.interrupt(); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(root.__transition, undefined); }); it("selection.interrupt() has no effect on an interrupting transition", async () => { const root = document.documentElement; let interrupts = 0; const s = select(root); const t = s.transition().duration(50).on("interrupt", interrupted); const schedule = root.__transition[t._id]; function interrupted() { ++interrupts; s.interrupt(); } await new Promise(resolve => timeout(() => { assert.strictEqual(schedule.state, STARTED); s.interrupt(); assert.strictEqual(schedule.state, ENDED); assert.strictEqual(schedule.timer._call, null); assert.strictEqual(interrupts, 1); resolve(); })); }); ================================================ FILE: test/selection/transition-test.js ================================================ import assert from "assert"; import {easeBounce, easeCubic} from "d3-ease"; import {select, selectAll} from "d3-selection"; import {now, timeout} from "d3-timer"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("selection.transition() returns an instanceof transition", () => { const root = document.documentElement; const s = select(root); const t = s.transition(); assert.strictEqual(t instanceof transition, true); }); it("selection.transition() uses the default timing parameters", () => { const root = document.documentElement; const s = select(root); const t = s.transition(); const schedule = root.__transition[t._id]; assert.strictEqual(schedule.time, now()); assert.strictEqual(schedule.delay, 0); assert.strictEqual(schedule.duration, 250); assert.strictEqual(schedule.ease, easeCubic); }); it("selection.transition() assigns a monotonically-increasing id", () => { const root = document.documentElement; const s = select(root); const t1 = s.transition(); const t2 = s.transition(); const t3 = s.transition(); assert(t2._id > t1._id); assert(t3._id > t2._id); }); it("selection.transition() uses a default name of null", () => { const root = document.documentElement; const s = select(root); const t = s.transition(); const schedule = root.__transition[t._id]; assert.strictEqual(schedule.name, null); }); it("selection.transition(null) uses a name of null", () => { const root = document.documentElement; const s = select(root); const t = s.transition(null); const schedule = root.__transition[t._id]; assert.strictEqual(schedule.name, null); }); it("selection.transition(undefined) uses a name of null", () => { const root = document.documentElement; const s = select(root); const t = s.transition(undefined); const schedule = root.__transition[t._id]; assert.strictEqual(schedule.name, null); }); it("selection.transition(name) uses the specified name", () => { const root = document.documentElement; const s = select(root); const t = s.transition("foo"); const schedule = root.__transition[t._id]; assert.strictEqual(schedule.name, "foo"); }); it("selection.transition(name) coerces the name to a string", () => { const root = document.documentElement; const s = select(root); const t = s.transition({toString() { return "foo"; }}); const schedule = root.__transition[t._id]; assert.strictEqual(schedule.name, "foo"); }); it("selection.transition(transition) inherits the id, name and timing from the corresponding parent in the specified transition", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const s = selectAll([one, two]); const t = s.transition().delay(function(d, i) { return i * 50; }).duration(100).ease(easeBounce); const schedule1 = one.__transition[t._id]; const schedule2 = two.__transition[t._id]; const t1b = select(one.firstChild).transition(t); const schedule1b = one.firstChild.__transition[t._id]; assert.strictEqual(t1b._id, t._id); assert.strictEqual(schedule1b.name, schedule1.name); assert.strictEqual(schedule1b.delay, schedule1.delay); assert.strictEqual(schedule1b.duration, schedule1.duration); assert.strictEqual(schedule1b.ease, schedule1.ease); assert.strictEqual(schedule1b.time, schedule1.time); await new Promise(resolve => timeout(resolve, 10)); const t2b = select(two.firstChild).transition(t); const schedule2b = two.firstChild.__transition[t._id]; assert.strictEqual(t2b._id, t._id); assert.strictEqual(schedule2b.name, schedule2.name); assert.strictEqual(schedule2b.delay, schedule2.delay); assert.strictEqual(schedule2b.duration, schedule2.duration); assert.strictEqual(schedule2b.ease, schedule2.ease); assert.strictEqual(schedule2b.time, schedule2.time); }); it("selection.transition(transition) reselects the existing transition with the specified transition’s id, if any", () => { const root = document.documentElement; const foo = () => {}; const bar = () => {}; const s = select(root); const t1 = s.transition().tween("tween", foo); const schedule1 = root.__transition[t1._id]; const t2 = s.transition(t1).tween("tween", bar); const schedule2 = root.__transition[t2._id]; assert.strictEqual(t1._id, t2._id); assert.strictEqual(schedule1, schedule2); assert.strictEqual(t1.tween("tween"), bar); assert.strictEqual(t2.tween("tween"), bar); }); it("selection.transition(transition) throws an error if the specified transition is not found", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = select(one).transition(); const t2 = select(two).transition().delay(50); assert.throws(() => select(two).transition(t1), /transition .* not found/); assert.throws(() => select(one).transition(t2), /transition .* not found/); }); ================================================ FILE: test/transition/attr-test.js ================================================ import assert from "assert"; import {easeCubic} from "d3-ease"; import {interpolateNumber, interpolateRgb, interpolateString} from "d3-interpolate"; import {select, selectAll} from "d3-selection"; import {timeout} from "d3-timer"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.attr(name, value) creates an tween to the specified value", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("red", "blue"); const s = select(root).attr("fill", "red"); s.transition().attr("fill", "blue"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttribute("fill"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.attr(name, value) creates a namespaced tween to the specified value", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("red", "blue"); const s = select(root).attr("svg:fill", "red"); s.transition().attr("svg:fill", "blue"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttributeNS("http://www.w3.org/2000/svg", "fill"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.attr(name, value) creates an tween to the value returned by the specified function", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("red", "blue"); const s = select(root).attr("fill", "red"); s.transition().attr("fill", function() { return "blue"; }); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttribute("fill"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.attr(name, value) creates a namespaced tween to the value returned by the specified function", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("red", "blue"); const s = select(root).attr("svg:fill", "red"); s.transition().attr("svg:fill", function() { return "blue"; }); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttributeNS("http://www.w3.org/2000/svg", "fill"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.attr(name, constant) is a noop if the string-coerced value matches the current value on tween initialization", async () => { const root = document.documentElement; const s = select(root).attr("foo", 1); s.transition().attr("foo", 1); timeout(() => root.setAttribute("foo", 2), 125); await new Promise(resolve => timeout(() => { assert.strictEqual(root.getAttribute("foo"), "2"); resolve(); }, 250)); }); it("transition.attr(ns:name, constant) is a noop if the string-coerced value matches the current value on tween initialization", async () => { const root = document.documentElement; const s = select(root).attr("svg:foo", 1); s.transition().attr("svg:foo", 1); timeout(() => root.setAttributeNS("http://www.w3.org/2000/svg", "foo", 2), 125); await new Promise(resolve => timeout(() => { assert.strictEqual(root.getAttributeNS("http://www.w3.org/2000/svg", "foo"), "2"); resolve(); }, 250)); }); it("transition.attr(name, function) is a noop if the string-coerced value matches the current value on tween initialization", async () => { const root = document.documentElement; const s = select(root).attr("foo", 1); s.transition().attr("foo", function() { return 1; }); timeout(() => root.setAttribute("foo", 2), 125); await new Promise(resolve => timeout(() => { assert.strictEqual(root.getAttribute("foo"), "2"); resolve(); }, 250)); }); it("transition.attr(ns:name, function) is a noop if the string-coerced value matches the current value on tween initialization", async () => { const root = document.documentElement; const s = select(root).attr("svg:foo", 1); s.transition().attr("svg:foo", function() { return 1; }); timeout(() => root.setAttributeNS("http://www.w3.org/2000/svg", "foo", 2), 125); await new Promise(resolve => timeout(() => { assert.strictEqual(root.getAttributeNS("http://www.w3.org/2000/svg", "foo"), "2"); resolve(); }, 250)); }); it("transition.attr(name, constant) uses interpolateNumber if value is a number", async () => { const root = document.documentElement; const s = select(root).attr("foo", "15px"); s.transition().attr("foo", 10); await new Promise(resolve => timeout(() => { assert.strictEqual(root.getAttribute("foo"), "NaN"); resolve(); }, 125)); }); it("transition.attr(name, function) uses interpolateNumber if value is a number", async () => { const root = document.documentElement; const s = select(root).attr("foo", "15px"); s.transition().attr("foo", () => 10); await new Promise(resolve => timeout(() => { assert.strictEqual(root.getAttribute("foo"), "NaN"); resolve(); }, 125)); }); it("transition.attr(name, value) immediately evaluates the specified function with the expected context and arguments", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const ease = easeCubic; const duration = 250; const interpolate1 = interpolateRgb("cyan", "red"); const interpolate2 = interpolateRgb("magenta", "green"); const s = selectAll([one, two]).data(["red", "green"]); const result = []; s.transition().attr("fill", function(d, i, nodes) { result.push([d, i, nodes, this]); return d; }); assert.deepStrictEqual(result, [ ["red", 0, [one, two], one], ["green", 1, [one, two], two] ]); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(one.getAttribute("fill"), interpolate1(ease(elapsed / duration))); assert.strictEqual(two.getAttribute("fill"), interpolate2(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.attr(name, value) constructs an interpolator using the current value on start", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("red", "blue"); const s = select(root); s.transition().on("start", function() { s.attr("fill", "red"); }).attr("fill", function() { return "blue"; }); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttribute("fill"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.attr(name, null) creates an tween which removes the specified attribute post-start", async () => { const root = document.documentElement; const s = select(root).attr("fill", "red"); const started = () => assert.strictEqual(root.getAttribute("fill"), "red"); s.transition().attr("fill", null).on("start", started); await new Promise(resolve => timeout(() => { assert.strictEqual(root.hasAttribute("fill"), false); resolve(); })); }); it("transition.attr(name, null) creates an tween which removes the specified namespaced attribute post-start", async () => { const root = document.documentElement; const s = select(root).attr("svg:fill", "red"); const started = () => assert.strictEqual(root.getAttributeNS("http://www.w3.org/2000/svg", "fill"), "red"); s.transition().attr("svg:fill", null).on("start", started); await new Promise(resolve => timeout(() => { assert.strictEqual(root.hasAttributeNS("http://www.w3.org/2000/svg", "fill"), false); resolve(); })); }); it("transition.attr(name, value) creates an tween which removes the specified attribute post-start if the specified function returns null", async () => { const root = document.documentElement; const s = select(root).attr("fill", "red"); const started = () => assert.strictEqual(root.getAttribute("fill"), "red"); s.transition().attr("fill", function() {}).on("start", started); await new Promise(resolve => timeout(() => { assert.strictEqual(root.hasAttribute("fill"), false); resolve(); })); }); it("transition.attr(name, value) creates an tween which removes the specified namespaced attribute post-start if the specified function returns null", async () => { const root = document.documentElement; const s = select(root).attr("svg:fill", "red"); const started = () => assert.strictEqual(root.getAttributeNS("http://www.w3.org/2000/svg", "fill"), "red"); s.transition().attr("svg:fill", function() {}).on("start", started); await new Promise(resolve => timeout(() => { assert.strictEqual(root.hasAttributeNS("http://www.w3.org/2000/svg", "fill"), false); resolve(); })); }); it("transition.attr(name, value) interpolates numbers", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateNumber(1, 2); const s = select(root).attr("foo", 1); s.transition().attr("foo", 2); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttribute("foo"), interpolate(ease(elapsed / duration)) + ""); resolve(); }, 125)); }); it("transition.attr(name, value) interpolates strings", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateString("1px", "2px"); const s = select(root).attr("foo", "1px"); s.transition().attr("foo", "2px"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttribute("foo"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.attr(name, value) interpolates colors", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("#f00", "#00f"); const s = select(root).attr("foo", "#f00"); s.transition().attr("foo", "#00f"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttribute("foo"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.attr(name, value) creates an attrTween with the specified name", async () => { const root = document.documentElement; const s = select(root).attr("fill", "red"); const t = s.transition().attr("fill", "blue"); assert.strictEqual(t.attrTween("fill").call(root).call(root, 0.5), "rgb(128, 0, 128)"); }); it("transition.attr(name, value) creates a tween with the name \"attr.name\"", async () => { const root = document.documentElement; const s = select(root).attr("fill", "red"); const t = s.transition().attr("fill", "blue"); t.tween("attr.fill").call(root).call(root, 0.5); assert.strictEqual(root.getAttribute("fill"), "rgb(128, 0, 128)"); }); ================================================ FILE: test/transition/attrTween-test.js ================================================ import assert from "assert"; import {easeCubic} from "d3-ease"; import {interpolateHcl} from "d3-interpolate"; import {select, selectAll} from "d3-selection"; import {timeout, now} from "d3-timer"; import "../../src/index.js"; import {ENDING} from "../../src/transition/schedule.js"; import it from "../jsdom.js"; it("transition.attrTween(name, value) defines an attribute tween using the interpolator returned by the specified function", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); select(root).transition().attrTween("foo", () => interpolate); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttribute("foo"), interpolate(easeCubic(elapsed / 250))); resolve(); }, 125)); }); it("transition.attrTween(name, value) invokes the value function with the expected context and arguments", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const result = []; selectAll([one, two]).data(["one", "two"]).transition().attrTween("foo", function(d, i, nodes) { result.push([d, i, nodes, this]); }); await new Promise(resolve => timeout(resolve)); assert.deepStrictEqual(result, [ ["one", 0, [one, two], one], ["two", 1, [one, two], two] ]); }); it("transition.attrTween(name, value) passes the eased time to the interpolator", async () => { const root = document.documentElement; const then = now(); const duration = 250; const ease = easeCubic; const t = select(root).transition().attrTween("foo", () => interpolate); const schedule = root.__transition[t._id]; function interpolate(t) { assert.strictEqual(this, root); assert.strictEqual(t, schedule.state === ENDING ? 1 : ease((now() - then) / duration)); } await t.end(); }); it("transition.attrTween(name, value) allows the specified function to return null for a noop", async () => { const root = document.documentElement; const s = select(root).attr("foo", "42").attr("svg:bar", "43"); s.transition().attrTween("foo", () => {}).attrTween("svg:bar", () => {}); await new Promise(resolve => timeout(resolve, 125)); assert.strictEqual(root.getAttribute("foo"), "42"); assert.strictEqual(root.getAttributeNS("http://www.w3.org/2000/svg", "bar"), "43"); }); it("transition.attrTween(name, value) defines a namespaced attribute tween using the interpolator returned by the specified function", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); select(root).transition().attrTween("svg:foo", () => interpolate); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttributeNS("http://www.w3.org/2000/svg", "foo"), interpolate(easeCubic(elapsed / 250))); resolve(); }, 125)); }); it("transition.attrTween(name, value) coerces the specified name to a string", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); select(root).transition().attrTween({toString() { return "foo"; }}, () => interpolate); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.getAttribute("foo"), interpolate(easeCubic(elapsed / 250))); resolve(); }, 125)); }); it("transition.attrTween(name, value) throws an error if value is not null and not a function", async () => { const root = document.documentElement; const t = select(root).transition(); assert.throws(() => { t.attrTween("foo", 42); }); }); it("transition.attrTween(name, null) removes the specified attribute tween", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); const t = select(root).transition().attrTween("foo", () => interpolate).attrTween("foo", null); assert.strictEqual(t.attrTween("foo"), null); assert.strictEqual(t.tween("attr.foo"), null); await new Promise(resolve => timeout(resolve, 125)); assert.strictEqual(root.hasAttribute("foo"), false); }); it("transition.attrTween(name) returns the attribute tween with the specified name", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); const tween = () => interpolate; const started = () => assert.strictEqual(t.attrTween("foo"), tween); const ended = () => assert.strictEqual(t.attrTween("foo"), tween); const t = select(root).transition().attrTween("foo", tween).on("start", started).on("end", ended); assert.strictEqual(t.attrTween("foo"), tween); assert.strictEqual(t.attrTween("bar"), null); await t.end(); }); it("transition.attrTween(name) coerces the specified name to a string", async () => { const root = document.documentElement; const tween = () => {}; const t = select(root).transition().attrTween("color", tween); assert.strictEqual(t.attrTween({toString() { return "color"; }}), tween); }); ================================================ FILE: test/transition/call-test.js ================================================ import assert from "assert"; import {selection} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.call is the same as selection.call", () => { assert.strictEqual(transition.prototype.call, selection.prototype.call); }); ================================================ FILE: test/transition/delay-test.js ================================================ import assert from "assert"; import {select, selectAll} from "d3-selection"; import it from "../jsdom.js"; import "../../src/index.js"; it("transition.delay() returns the delay for the first non-null node", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = select(one).transition(); const t2 = select(two).transition().delay(50); assert.strictEqual(one.__transition[t1._id].delay, 0); assert.strictEqual(two.__transition[t2._id].delay, 50); assert.strictEqual(t1.delay(), 0); assert.strictEqual(t2.delay(), 50); assert.strictEqual(select(one).transition(t1).delay(), 0); assert.strictEqual(select(two).transition(t2).delay(), 50); assert.strictEqual(selectAll([null, one]).transition(t1).delay(), 0); assert.strictEqual(selectAll([null, two]).transition(t2).delay(), 50); }); it("transition.delay(number) sets the delay for each selected element to the specified number", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().delay(50); assert.strictEqual(one.__transition[t._id].delay, 50); assert.strictEqual(two.__transition[t._id].delay, 50); }); it("transition.delay(value) coerces the specified value to a number", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().delay("50"); assert.strictEqual(one.__transition[t._id].delay, 50); assert.strictEqual(two.__transition[t._id].delay, 50); }); it("transition.delay(function) passes the expected arguments and context to the function", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const result = []; const s = selectAll([one, two]).data(["one", "two"]); const t = s.transition().delay(function(d, i, nodes) { result.push([d, i, nodes, this]); }); assert.deepStrictEqual(result, [ ["one", 0, t._groups[0], one], ["two", 1, t._groups[0], two] ]); }); it("transition.delay(function) sets the delay for each selected element to the number returned by the specified function", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().delay(function(d, i) { return i * 20; }); assert.strictEqual(one.__transition[t._id].delay, 0); assert.strictEqual(two.__transition[t._id].delay, 20); }); it("transition.delay(function) coerces the value returned by the specified function to a number", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().delay(function(d, i) { return i * 20 + ""; }); assert.strictEqual(one.__transition[t._id].delay, 0); assert.strictEqual(two.__transition[t._id].delay, 20); }); ================================================ FILE: test/transition/duration-test.js ================================================ import assert from "assert"; import {select, selectAll} from "d3-selection"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.duration() returns the duration for the first non-null node", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = select(one).transition(); const t2 = select(two).transition().duration(50); assert.strictEqual(one.__transition[t1._id].duration, 250); assert.strictEqual(two.__transition[t2._id].duration, 50); assert.strictEqual(t1.duration(), 250); assert.strictEqual(t2.duration(), 50); assert.strictEqual(select(one).transition(t1).duration(), 250); assert.strictEqual(select(two).transition(t2).duration(), 50); assert.strictEqual(selectAll([null, one]).transition(t1).duration(), 250); assert.strictEqual(selectAll([null, two]).transition(t2).duration(), 50); }); it("transition.duration(number) sets the duration for each selected element to the specified number", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().duration(50); assert.strictEqual(one.__transition[t._id].duration, 50); assert.strictEqual(two.__transition[t._id].duration, 50); }); it("transition.duration(value) coerces the specified value to a number", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().duration("50"); assert.strictEqual(one.__transition[t._id].duration, 50); assert.strictEqual(two.__transition[t._id].duration, 50); }); it("transition.duration(function) passes the expected arguments and context to the function", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const result = []; const s = selectAll([one, two]).data(["one", "two"]); const t = s.transition().duration(function(d, i, nodes) { result.push([d, i, nodes, this]); }); assert.deepStrictEqual(result, [ ["one", 0, t._groups[0], one], ["two", 1, t._groups[0], two] ]); }); it("transition.duration(function) sets the duration for each selected element to the number returned by the specified function", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().duration(function(d, i) { return i * 20; }); assert.strictEqual(one.__transition[t._id].duration, 0); assert.strictEqual(two.__transition[t._id].duration, 20); }); it("transition.duration(function) coerces the value returned by the specified function to a number", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().duration(function(d, i) { return i * 20 + ""; }); assert.strictEqual(one.__transition[t._id].duration, 0); assert.strictEqual(two.__transition[t._id].duration, 20); }); ================================================ FILE: test/transition/each-test.js ================================================ import assert from "assert"; import {select, selectAll, selection} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.each is the same as selection.each", () => { assert.strictEqual(transition.prototype.each, selection.prototype.each); }); it("transition.each() runs as expected", () => { const root = document.documentElement; let a = 0; select(root).transition().each(() => { ++a; }); assert.strictEqual(a, 1); a = 0; selectAll([null, root]).transition().each(() => { ++a; }); assert.strictEqual(a, 1); }); ================================================ FILE: test/transition/ease-test.js ================================================ import assert from "assert"; import {easeBounce, easeCubic} from "d3-ease"; import {select, selectAll} from "d3-selection"; import {timeout} from "d3-timer"; import "../../src/index.js"; import {ENDING, RUNNING} from "../../src/transition/schedule.js"; import it from "../jsdom.js"; it("transition.ease() returns the easing function for the first non-null node", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = select(one).transition(); const t2 = select(two).transition().ease(easeBounce); assert.strictEqual(one.__transition[t1._id].ease, easeCubic); assert.strictEqual(two.__transition[t2._id].ease, easeBounce); assert.strictEqual(t1.ease(), easeCubic); assert.strictEqual(t2.ease(), easeBounce); assert.strictEqual(select(one).transition(t1).ease(), easeCubic); assert.strictEqual(select(two).transition(t2).ease(), easeBounce); assert.strictEqual(selectAll([null, one]).transition(t1).ease(), easeCubic); assert.strictEqual(selectAll([null, two]).transition(t2).ease(), easeBounce); }); it("transition.ease(ease) throws an error if ease is not a function", () => { const root = document.documentElement; const t = select(root).transition(); assert.throws(() => { t.ease(42); }); assert.throws(() => { t.ease(null); }); }); it("transition.ease(ease) sets the easing function for each selected element to the specified function", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().ease(easeBounce); assert.strictEqual(one.__transition[t._id].ease, easeBounce); assert.strictEqual(two.__transition[t._id].ease, easeBounce); }); it("transition.ease(ease) passes the easing function the normalized time in [0, 1]", async () => { let actual; const root = document.documentElement; const ease = t => { actual = t; return t; }; select(root).transition().ease(ease); await new Promise(resolve => timeout((elapsed) => { assert.strictEqual(actual, elapsed / 250); resolve() }, 100)); }); it("transition.ease(ease) does not invoke the easing function on the last frame", async () => { const root = document.documentElement; const ease = t => { assert.strictEqual(schedule.state, RUNNING); return t; }; const t = select(root).transition().ease(ease); const schedule = root.__transition[t._id]; await t.end(); }); it("transition.ease(ease) observes the eased time returned by the easing function", async () => { const root = document.documentElement; let expected; const ease = () => { return expected = Math.random() * 2 - 0.5; }; const tween = () => { return t => { assert.strictEqual(t, schedule.state === ENDING ? 1 : expected); }; }; const t = select(root).transition().ease(ease).tween("tween", tween); const schedule = root.__transition[t._id]; await t.end(); }); ================================================ FILE: test/transition/easeVarying-test.js ================================================ import assert from "assert"; import {easePolyIn} from "d3-ease"; import {select} from "d3-selection"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.easeVarying(factory) accepts an easing function factory", "

", () => { const t = select(document).selectAll("h1").data([{exponent: 3}, {exponent: 4}]).transition(); t.easeVarying(d => easePolyIn.exponent(d.exponent)); assert.strictEqual(t.ease()(0.5), easePolyIn.exponent(3)(0.5)); }); it("transition.easeVarying(factory) passes factory datum, index, group with the node as this", "

", () => { const t = select(document).selectAll("h1").data([{exponent: 3}, {exponent: 4}]).transition(); const results = []; t.easeVarying(function(d, i, e) { results.push([d, i, e, this]); return t => t; }); assert.deepStrictEqual(results, [ [{exponent: 3}, 0, [...t], document.querySelector("#one")], [{exponent: 4}, 1, [...t], document.querySelector("#two")], ]); }); it("transition.easeVarying() throws an error if the argument is not a function", "

", () => { const t = select(document).selectAll("h1").data([{exponent: 3}, {exponent: 4}]).transition(); assert.throws(() => { t.easeVarying(); }); assert.throws(() => { t.easeVarying("a"); }); }); ================================================ FILE: test/transition/empty-test.js ================================================ import assert from "assert"; import {selection} from "d3-selection"; import {transition} from "../../src/index.js"; it("transition.empty is the same as selection.empty", () => { assert.strictEqual(transition.prototype.empty, selection.prototype.empty); }); ================================================ FILE: test/transition/filter-test.js ================================================ import assert from "assert"; import {selectAll} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.filter(selector) retains the elements matching the specified selector", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([one, two]).data([1, 2]).transition().delay(function(d) { return d * 10; }); const t2 = t1.filter("#two"); assert.strictEqual(t2 instanceof transition, true); assert.deepStrictEqual(t2._groups, [[two]]); assert.strictEqual(t2._parents, t1._parents); assert.strictEqual(t2._name, t1._name); assert.strictEqual(t2._id, t1._id); }); it("transition.filter(function) retains the elements for which the specified function returns true", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([one, two]).data([1, 2]).transition().delay(function(d) { return d * 10; }); const t2 = t1.filter(function() { return this === two; }); assert.strictEqual(t2 instanceof transition, true); assert.deepStrictEqual(t2._groups, [[two]]); assert.strictEqual(t2._parents, t1._parents); assert.strictEqual(t2._name, t1._name); assert.strictEqual(t2._id, t1._id); }); ================================================ FILE: test/transition/index-test.js ================================================ import assert from "assert"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition() returns a transition on the document element with the null name", () => { const root = document.documentElement; const t = transition(); const schedule = root.__transition[t._id]; assert.strictEqual(t.node(), root); assert.strictEqual(schedule.name, null); }); it("transition(null) returns a transition on the document element with the null name", () => { const root = document.documentElement; const t = transition(null); const schedule = root.__transition[t._id]; assert.strictEqual(t.node(), root); assert.strictEqual(schedule.name, null); }); it("transition(undefined) returns a transition on the document element with the null name", () => { const root = document.documentElement; const t = transition(undefined); const schedule = root.__transition[t._id]; assert.strictEqual(t.node(), root); assert.strictEqual(schedule.name, null); }); it("transition(name) returns a transition on the document element with the specified name", () => { const root = document.documentElement; const t = transition("foo"); const schedule = root.__transition[t._id]; assert.strictEqual(t.node(), root); assert.strictEqual(schedule.name, "foo"); }); it("transition.prototype can be extended", () => { try { let pass = 0; transition.prototype.test = () => { return ++pass; }; assert.strictEqual(transition().test(), 1); assert.strictEqual(pass, 1); } finally { delete transition.prototype.test; } }); it("transitions are instanceof transition", () => { assert.strictEqual(transition() instanceof transition, true); }); ================================================ FILE: test/transition/merge-test.js ================================================ import assert from "assert"; import {select, selectAll} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.merge(other) merges elements from the specified other transition for null elements in this transition", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t0 = select(document.documentElement).transition(); const t1 = selectAll([null, two]).transition(t0); const t2 = selectAll([one, null]).transition(t0); const t3 = t1.merge(t2); assert.strictEqual(t3 instanceof transition, true); assert.deepStrictEqual(t3._groups, [[one, two]]); assert.strictEqual(t3._parents, t1._parents); assert.strictEqual(t3._name, t1._name); assert.strictEqual(t3._id, t1._id); }); it("transition.merge(other) throws an error if the other transition has a different id", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([null, two]).transition(); const t2 = selectAll([one, null]).transition(); assert.throws(() => { t1.merge(t2); }); }); ================================================ FILE: test/transition/node-test.js ================================================ import assert from "assert"; import {selection} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.node is the same as selection.node", () => { assert.strictEqual(transition.prototype.node, selection.prototype.node); }); ================================================ FILE: test/transition/nodes-test.js ================================================ import assert from "assert"; import {selection} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.nodes is the same as selection.nodes", () => { assert.strictEqual(transition.prototype.nodes, selection.prototype.nodes); }); ================================================ FILE: test/transition/on-test.js ================================================ import assert from "assert"; import {select, selectAll} from "d3-selection"; import {timeout} from "d3-timer"; import "../../src/index.js"; import {ENDED, ENDING, STARTING} from "../../src/transition/schedule.js"; import it from "../jsdom.js"; it("transition.on(type, listener) throws an error if listener is not a function", async () => { const root = document.documentElement; const t = select(root).transition(); assert.throws(() => { t.on("start", 42); }); }); it("transition.on(typename) returns the listener with the specified typename, if any", async () => { const root = document.documentElement; const foo = () => {}; const bar = () => {}; const t = select(root).transition().on("start", foo).on("start.bar", bar); assert.strictEqual(t.on("start"), foo); assert.strictEqual(t.on("start.foo"), undefined); assert.strictEqual(t.on("start.bar"), bar); assert.strictEqual(t.on("end"), undefined); }); it("transition.on(typename) throws an error if the specified type is not supported", async () => { const root = document.documentElement; const t = select(root).transition(); assert.throws(() => { t.on("foo"); }); }); it("transition.on(typename, listener) throws an error if the specified type is not supported", async () => { const root = document.documentElement; const t = select(root).transition(); assert.throws(() => { t.on("foo", () => {}); }); }); it("transition.on(typename, listener) throws an error if the specified listener is not a function", async () => { const root = document.documentElement; const t = select(root).transition(); assert.throws(() => { t.on("foo", 42); }); }); it("transition.on(typename, null) removes the listener with the specified typename, if any", async () => { const root = document.documentElement; let starts = 0; const t = select(root).transition().on("start.foo", () => { ++starts; }); const schedule = root.__transition[t._id]; assert.strictEqual(t.on("start.foo", null), t); assert.strictEqual(t.on("start.foo"), undefined); assert.strictEqual(schedule.on.on("start.foo"), undefined); await new Promise(resolve => timeout(resolve)); assert.strictEqual(starts, 0); }); it("transition.on(\"start\", listener) registers a listener for the start event", async () => { const root = document.documentElement; const t = select(root).transition(); const schedule = root.__transition[t._id]; await new Promise(resolve => t.on("start", () => { assert.strictEqual(schedule.state, STARTING) resolve(); })); }); it("transition.on(\"interrupt\", listener) registers a listener for the interrupt event (during start)", async () => { const root = document.documentElement; const s = select(root); const t = s.transition(); const schedule = root.__transition[t._id]; timeout(() => s.interrupt()); await new Promise(resolve => t.on("interrupt", () => { assert.strictEqual(schedule.state, ENDED); resolve(); })); }); it("transition.on(\"interrupt\", listener) registers a listener for the interrupt event (during run)", async () => { const root = document.documentElement; const s = select(root); const t = s.transition(); const schedule = root.__transition[t._id]; timeout(() => s.interrupt(), 50); await new Promise(resolve => t.on("interrupt", () => { assert.strictEqual(schedule.state, ENDED); resolve(); })); }); it("transition.on(\"end\", listener) registers a listener for the end event", async () => { const root = document.documentElement; const t = select(root).transition().duration(50); const schedule = root.__transition[t._id]; await new Promise(resolve => t.on("end", () => { assert.strictEqual(schedule.state, ENDING); resolve(); })); }); it("transition.on(typename, listener) uses copy-on-write to apply changes", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const foo = () => {}; const bar = () => {}; const t = selectAll([one, two]).transition(); const schedule1 = one.__transition[t._id]; const schedule2 = two.__transition[t._id]; t.on("start", foo); assert.strictEqual(schedule1.on.on("start"), foo); assert.strictEqual(schedule2.on, schedule1.on); t.on("start", bar); assert.strictEqual(schedule1.on.on("start"), bar); assert.strictEqual(schedule2.on, schedule1.on); select(two).transition(t).on("start", foo); assert.strictEqual(schedule1.on.on("start"), bar); assert.strictEqual(schedule2.on.on("start"), foo); }); ================================================ FILE: test/transition/remove-test.js ================================================ import assert from "assert"; import {select} from "d3-selection"; import {timeout} from "d3-timer"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.remove() creates an end listener to remove the element", async () => { const root = document.documentElement; const body = document.body; const s = select(body); const t = s.transition().remove().on("start", started).on("end", ended); const end = t.end(); function started() { assert.strictEqual(body.parentNode, root); } function ended() { assert.strictEqual(body.parentNode, null); } await new Promise(resolve => timeout(resolve)); assert.strictEqual(body.parentNode, root); await end; }); it("transition.remove() creates an end listener named end.remove", async () => { const root = document.documentElement; const body = document.body; const s = select(body); const t = s.transition().remove().on("start", started).on("end", ended); const end = t.end(); function started() { assert.strictEqual(body.parentNode, root); } function ended() { assert.strictEqual(body.parentNode, root); } t.on("end.remove").call(body); assert.strictEqual(body.parentNode, null); t.on("end.remove", null); root.appendChild(body); await end; }); ================================================ FILE: test/transition/select-test.js ================================================ import assert from "assert"; import {selectAll} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.select(selector) selects the descendants matching the specified selector, then derives a transition", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([one, two]).data([1, 2]).transition().delay(function(d) { return d * 10; }); const t2 = t1.select("child"); assert.strictEqual(t2 instanceof transition, true); assert.deepStrictEqual(t2._groups, [[one.firstChild, two.firstChild]]); assert.strictEqual(t2._parents, t1._parents); assert.strictEqual(t2._name, t1._name); assert.strictEqual(t2._id, t1._id); assert.strictEqual(one.firstChild.__data__, 1); assert.strictEqual(two.firstChild.__data__, 2); assert.strictEqual(one.firstChild.__transition[t1._id].delay, 10); assert.strictEqual(two.firstChild.__transition[t1._id].delay, 20); }); it("transition.select(function) selects the descendants returned by the specified function, then derives a transition", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([one, two]).data([1, 2]).transition().delay(function(d) { return d * 10; }); const t2 = t1.select(function() { return this.firstChild; }); assert.strictEqual(t2 instanceof transition, true); assert.deepStrictEqual(t2._groups, [[one.firstChild, two.firstChild]]); assert.strictEqual(t2._parents, t1._parents); assert.strictEqual(t2._name, t1._name); assert.strictEqual(t2._id, t1._id); assert.strictEqual(one.firstChild.__data__, 1); assert.strictEqual(two.firstChild.__data__, 2); assert.strictEqual(one.firstChild.__transition[t1._id].delay, 10); assert.strictEqual(two.firstChild.__transition[t1._id].delay, 20); }); ================================================ FILE: test/transition/selectAll-test.js ================================================ import assert from "assert"; import {selectAll} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.selectAll(selector) selects the descendants matching the specified selector, then derives a transition", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([one, two]).data([1, 2]).transition().delay(function(d) { return d * 10; }); const t2 = t1.selectAll("child"); assert.strictEqual(t2 instanceof transition, true); assert.deepStrictEqual(t2._groups.map(group => Array.from(group)), [[one.firstChild], [two.firstChild]]); assert.deepStrictEqual(t2._parents, [one, two]); assert.strictEqual(t2._name, t1._name); assert.strictEqual(t2._id, t1._id); assert.strictEqual("__data__" in one.firstChild, false); assert.strictEqual("__data__" in two.firstChild, false); assert.strictEqual(one.firstChild.__transition[t1._id].delay, 10); assert.strictEqual(two.firstChild.__transition[t1._id].delay, 20); }); it("transition.selectAll(function) selects the descendants returned by the specified function, then derives a transition", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([one, two]).data([1, 2]).transition().delay(function(d) { return d * 10; }); const t2 = t1.selectAll(function() { return [this.firstChild]; }); assert.strictEqual(t2 instanceof transition, true); assert.deepStrictEqual(t2._groups, [[one.firstChild], [two.firstChild]]); assert.deepStrictEqual(t2._parents, [one, two]); assert.strictEqual(t2._name, t1._name); assert.strictEqual(t2._id, t1._id); assert.strictEqual("__data__" in one.firstChild, false); assert.strictEqual("__data__" in two.firstChild, false); assert.strictEqual(one.firstChild.__transition[t1._id].delay, 10); assert.strictEqual(two.firstChild.__transition[t1._id].delay, 20); }); ================================================ FILE: test/transition/selectChild-test.js ================================================ import assert from "assert"; import {selectAll} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.selectChild(selector) selects the child matching the specified selector, then derives a transition", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([one, two]).data([1, 2]).transition().delay(function(d) { return d * 10; }); const t2 = t1.selectChild("child"); assert.strictEqual(t2 instanceof transition, true); assert.deepStrictEqual(t2._groups, [[one.firstChild, two.firstChild]]); assert.strictEqual(t2._parents, t1._parents); assert.strictEqual(t2._name, t1._name); assert.strictEqual(t2._id, t1._id); assert.strictEqual(one.firstChild.__data__, 1); assert.strictEqual(two.firstChild.__data__, 2); assert.strictEqual(one.firstChild.__transition[t1._id].delay, 10); assert.strictEqual(two.firstChild.__transition[t1._id].delay, 20); }); ================================================ FILE: test/transition/selectChildren-test.js ================================================ import assert from "assert"; import {selectAll} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.selectChildren(selector) selects the children matching the specified selector, then derives a transition", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t1 = selectAll([one, two]).data([1, 2]).transition().delay(function(d) { return d * 10; }); const t2 = t1.selectChildren("child"); assert.strictEqual(t2 instanceof transition, true); assert.deepStrictEqual(t2._groups.map(group => Array.from(group)), [[one.firstChild], [two.firstChild]]); assert.deepStrictEqual(t2._parents, [one, two]); assert.strictEqual(t2._name, t1._name); assert.strictEqual(t2._id, t1._id); assert.strictEqual("__data__" in one.firstChild, false); assert.strictEqual("__data__" in two.firstChild, false); assert.strictEqual(one.firstChild.__transition[t1._id].delay, 10); assert.strictEqual(two.firstChild.__transition[t1._id].delay, 20); }); ================================================ FILE: test/transition/selection-test.js ================================================ import assert from "assert"; import {select, selection} from "d3-selection"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.selection() returns the transition’s selection", "

one

two

", () => { const s0 = select(document.body).selectAll("h1"); const t = s0.transition(); const s1 = t.selection(); assert(s1 instanceof selection); assert.strictEqual(s1._groups, s0._groups); assert.strictEqual(s1._parents, s0._parents); }); ================================================ FILE: test/transition/size-test.js ================================================ import assert from "assert"; import {select, selectAll, selection} from "d3-selection"; import {transition} from "../../src/index.js"; import it from "../jsdom.js"; it("transition.size is the same as selection.size", () => { assert.strictEqual(transition.prototype.size, selection.prototype.size); }); it("transition.size() returns the expected value", () => { const root = document.documentElement; assert.strictEqual(select(root).transition().size(), 1); assert.strictEqual(selectAll([null, root]).transition().size(), 1); }); ================================================ FILE: test/transition/style-test.js ================================================ import assert from "assert"; import {easeCubic} from "d3-ease"; import {interpolateNumber, interpolateRgb, interpolateString} from "d3-interpolate"; import {select, selectAll} from "d3-selection"; import {timeout} from "d3-timer"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.style(name, value) creates an tween to the specified value", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("red", "blue"); const s = select(root).style("color", "red"); s.transition().style("color", "blue"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.style.getPropertyValue("color"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.style(name, value) creates an tween to the value returned by the specified function", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("red", "blue"); const s = select(root).style("color", "red"); s.transition().style("color", () => "blue"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.style.getPropertyValue("color"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.style(name, value) immediately evaluates the specified function with the expected context and arguments", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const ease = easeCubic; const duration = 250; const interpolate1 = interpolateRgb("cyan", "red"); const interpolate2 = interpolateRgb("magenta", "green"); const t = selectAll([one, two]).data(["red", "green"]); const result = []; t.transition().style("color", function(d, i, nodes) { result.push([d, i, nodes, this]); return d; }); assert.deepStrictEqual(result, [ ["red", 0, [one, two], one], ["green", 1, [one, two], two] ]); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(one.style.getPropertyValue("color"), interpolate1(ease(elapsed / duration))); assert.strictEqual(two.style.getPropertyValue("color"), interpolate2(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.style(name, value) recycles tweens ", "

", () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const t = selectAll([one, two]).transition().style("color", "red"); assert.strictEqual(one.__transition[t._id].tween, two.__transition[t._id].tween); }); it("transition.style(name, value) constructs an interpolator using the current value on start", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("red", "blue"); const s = select(root); s.transition().on("start", () => { s.style("color", "red"); }).style("color", () => "blue"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.style.getPropertyValue("color"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.style(name, null) creates an tween which removes the specified style post-start", async () => { const root = document.documentElement; const started = () => assert.strictEqual(root.style.getPropertyValue("color"), "red"); const s = select(root).style("color", "red"); s.transition().style("color", null).on("start", started); await new Promise(resolve => timeout(() => { assert.strictEqual(root.style.getPropertyValue("color"), ""); resolve(); })); }); it("transition.style(name, null) creates an tween which removes the specified style post-start", async () => { const root = document.documentElement; const started = () => assert.strictEqual(root.style.getPropertyValue("color"), "red"); const selection = select(root).style("color", "red"); selection.transition().style("color", () => null).on("start", started); await new Promise(resolve => timeout(() => { assert.strictEqual(root.style.getPropertyValue("color"), ""); resolve(); })); }); it("transition.style(name, value) creates an tween which removes the specified style post-start if the specified function returns null", async () => { const root = document.documentElement; const started = () => assert.strictEqual(root.style.getPropertyValue("color"), "red"); const selection = select(root).style("color", "red"); selection.transition().style("color", function() {}).on("start", started); await new Promise(resolve => timeout(() => { assert.strictEqual(root.style.getPropertyValue("color"), ""); resolve(); })); }); it("transition.style(name, constant) is a noop if the string-coerced value matches the current value on tween initialization", async () => { const root = document.documentElement; const selection = select(root).style("opacity", 1); selection.transition().style("opacity", 1); timeout(() => { root.style.opacity = 0.5; }, 125); await new Promise(resolve => timeout(() => { assert.strictEqual(root.style.getPropertyValue("opacity"), "0.5"); resolve(); }, 250)); }); it("transition.style(name, function) is a noop if the string-coerced value matches the current value on tween initialization", async () => { const root = document.documentElement; const selection = select(root).style("opacity", 1); selection.transition().style("opacity", function() { return 1; }); timeout(() => { root.style.opacity = 0.5; }, 125); await new Promise(resolve => timeout(() => { assert.strictEqual(root.style.getPropertyValue("opacity"), "0.5"); resolve(); }, 250)); }); it("transition.style(name, value) interpolates numbers", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateNumber(0, 1); const s = select(root).style("opacity", 0); s.transition().style("opacity", 1); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.style.getPropertyValue("opacity"), interpolate(ease(elapsed / duration)) + ""); resolve(); }, 125)); }); it("transition.style(name, constant) uses interpolateNumber if value is a number", async () => { const root = document.documentElement; const s = select(root).style("font-size", "15px"); s.transition().style("font-size", 10); await new Promise(resolve => timeout(() => { assert.strictEqual(root.style.getPropertyValue("font-size"), "15px"); // ignores NaN resolve(); }, 125)); }); it("transition.style(name, function) uses interpolateNumber if value is a number", async () => { const root = document.documentElement; const s = select(root).style("font-size", "15px"); s.transition().style("font-size", () => 10); await new Promise(resolve => timeout(() => { assert.strictEqual(root.style.getPropertyValue("font-size"), "15px"); // ignores NaN resolve(); }, 125)); }); it("transition.style(name, value) interpolates strings", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateString("1px", "2px"); const s = select(root).style("font-size", "1px"); s.transition().style("font-size", "2px"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.style.getPropertyValue("font-size"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.style(name, value) interpolates colors", async () => { const root = document.documentElement; const ease = easeCubic; const duration = 250; const interpolate = interpolateRgb("#f00", "#00f"); const s = select(root).style("color", "#f00"); s.transition().style("color", "#00f"); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(root.style.getPropertyValue("color"), interpolate(ease(elapsed / duration))); resolve(); }, 125)); }); it("transition.style(name, value) creates an styleTween with the specified name", async () => { const root = document.documentElement; const s = select(root).style("color", "red"); const t = s.transition().style("color", "blue"); assert.strictEqual(t.styleTween("color").call(root).call(root, 0.5), "rgb(128, 0, 128)"); }); it("transition.style(name, value) creates a tween with the name \"style.name\"", async () => { const root = document.documentElement; const s = select(root).style("color", "red"); const t = s.transition().style("color", "blue"); t.tween("style.color").call(root).call(root, 0.5); assert.strictEqual(root.style.getPropertyValue("color"), "rgb(128, 0, 128)"); }); ================================================ FILE: test/transition/styleTween-test.js ================================================ import assert from "assert"; import {easeCubic} from "d3-ease"; import {interpolateHcl} from "d3-interpolate"; import {select, selectAll} from "d3-selection"; import {now, timeout} from "d3-timer"; import "../../src/index.js"; import {ENDING} from "../../src/transition/schedule.js"; import it from "../jsdom.js"; it("transition.styleTween(name, value) defines a style tween using the interpolator returned by the specified function", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); const ease = easeCubic; select(root).transition().styleTween("color", () => interpolate); await new Promise(resolve => timeout(elapsed => { assert.deepStrictEqual(root.style.getPropertyValue("color"), interpolate(ease(elapsed / 250))); assert.deepStrictEqual(root.style.getPropertyPriority("color"), ""); resolve(); }, 125)); }); it("transition.styleTween(name, value, priority) defines a style tween using the interpolator returned by the specified function", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); const ease = easeCubic; select(root).transition().styleTween("color", () => interpolate, "important"); await new Promise(resolve => timeout(elapsed => { assert.deepStrictEqual(root.style.getPropertyValue("color"), interpolate(ease(elapsed / 250))); assert.deepStrictEqual(root.style.getPropertyPriority("color"), "important"); resolve(); }, 125)); }); it("transition.styleTween(name, value) invokes the value function with the expected context and arguments", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const result = []; selectAll([one, two]).data(["one", "two"]).transition().styleTween("color", function(d, i, nodes) { result.push([d, i, nodes, this]); }); await new Promise(resolve => timeout(resolve)); assert.deepStrictEqual(result, [ ["one", 0, [one, two], one], ["two", 1, [one, two], two] ]); }); it("transition.styleTween(name, value) passes the eased time to the interpolator", async () => { const root = document.documentElement; const then = now(); const duration = 250; const ease = easeCubic; const t = select(root).transition().styleTween("color", () => interpolate); const schedule = root.__transition[t._id]; function interpolate(t) { assert.strictEqual(this, root); assert.strictEqual(t, schedule.state === ENDING ? 1 : ease((now() - then) / duration)); } await t.end(); }); it("transition.styleTween(name, value) allows the specified function to return null for a noop", async () => { const root = document.documentElement; const s = select(root).style("color", "red"); s.transition().styleTween("color", () => {}); await new Promise(resolve => timeout(() => { assert.deepStrictEqual(root.style.getPropertyValue("color"), "red"); resolve(); }, 125)); }); it("transition.styleTween(name, value) coerces the specified name to a string", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); const ease = easeCubic; select(root).transition().styleTween({toString() { return "color"; }}, () => interpolate); await new Promise(resolve => timeout(elapsed => { assert.deepStrictEqual(root.style.getPropertyValue("color"), interpolate(ease(elapsed / 250))); resolve(); }, 125)); }); it("transition.styleTween(name, value) throws an error if value is not null and not a function", async () => { const root = document.documentElement; const t = select(root).transition(); assert.throws(() => { t.styleTween("color", 42); }); }); it("transition.styleTween(name, null) removes the specified style tween", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); const t = select(root).transition().styleTween("color", () => interpolate).styleTween("color", null); assert.strictEqual(t.styleTween("color"), null); assert.strictEqual(t.tween("style.color"), null); await new Promise(resolve => timeout(() => { assert.strictEqual(root.style.getPropertyValue("color"), ""); resolve(); }, 125)); }); it("transition.styleTween(name) returns the style tween with the specified name", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); const tween = () => interpolate; const started = () => { assert.strictEqual(t.styleTween("color"), tween); }; const ended = () => { assert.strictEqual(t.styleTween("color"), tween); }; const t = select(root).transition().styleTween("color", tween).on("start", started).on("end", ended); assert.strictEqual(t.styleTween("color"), tween); assert.strictEqual(t.styleTween("bar"), null); await t.end(); }); it("transition.styleTween(name) coerces the specified name to a string", async () => { const root = document.documentElement; const tween = () => {}; const t = select(root).transition().styleTween("color", tween); assert.strictEqual(t.styleTween({toString() { return "color"; }}), tween); }); ================================================ FILE: test/transition/text-test.js ================================================ import assert from "assert"; import {select, selectAll} from "d3-selection"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.text(value) creates a tween to set the text content to the specified value post-start", async () => { const root = document.documentElement; const s = select(root); const t = s.transition().text("hello"); await new Promise(resolve => t.on("start", () => { assert.strictEqual(root.textContent, ""); resolve(); })); assert.strictEqual(root.textContent, "hello"); }); it("transition.text(value) creates a tween to set the text content to the value returned by the specified function post-start", async () => { const root = document.documentElement; const s = select(root); const t = s.transition().text(() => "hello"); await new Promise(resolve => t.on("start", () => { assert.strictEqual(root.textContent, ""); resolve(); })); assert.strictEqual(root.textContent, "hello"); }); it("transition.text(value) immediately evaluates the specified function with the expected context and arguments", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const s = selectAll([one, two]).data(["red", "green"]); const result = []; const t = s.transition().text(function(d, i, nodes) { result.push([d, i, nodes, this]); return d; }); assert.deepStrictEqual(result, [ ["red", 0, [one, two], one], ["green", 1, [one, two], two] ]); await new Promise(resolve => t.on("start", resolve)); assert.strictEqual(one.textContent, "red"); assert.strictEqual(two.textContent, "green"); }); it("transition.text(value) creates a tween with the name \"text\"", () => { const root = document.documentElement; const s = select(root); const t = s.transition().text("hello"); assert.strictEqual(t.tween("text").call(root), undefined); assert.strictEqual(root.textContent, "hello"); }); ================================================ FILE: test/transition/textTween-test.js ================================================ import assert from "assert"; import {easeCubic} from "d3-ease"; import {interpolateHcl} from "d3-interpolate"; import {select} from "d3-selection"; import {timeout} from "d3-timer"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.textTween(value) defines a text tween using the interpolator returned by the specified function", async () => { const root = document.documentElement; const interpolate = interpolateHcl("red", "blue"); const ease = easeCubic; select(root).transition().textTween(() => interpolate); await new Promise(resolve => timeout(elapsed => { assert.deepStrictEqual(root.textContent, interpolate(ease(elapsed / 250))); resolve(); }, 125)); }); it("transition.textTween() returns the existing text tween", () => { const root = document.documentElement; const factory = () => {}; const t = select(root).transition().textTween(factory); assert.strictEqual(t.textTween(), factory); }); it("transition.textTween(null) removes an existing text tween", () => { const root = document.documentElement; const factory = () => {}; const t = select(root).transition().textTween(factory); t.textTween(undefined); assert.strictEqual(t.textTween(), null); }); ================================================ FILE: test/transition/transition-test.js ================================================ import assert from "assert"; import {select} from "d3-selection"; import {timeout} from "d3-timer"; import "../../src/index.js"; import it from "../jsdom.js"; it("transition.transition() allows preceeding transitions with zero duration to end naturally", async () => { let end0 = false; let end1 = false; let end2 = false; const s = select(document.documentElement); const t = s.transition().duration(0).on("end", () => { end0 = true; }); s.transition().duration(0).on("end", () => { end1 = true; }); t.transition().duration(0).on("end", () => { end2 = true; }); await new Promise(resolve => timeout(resolve, 50)); assert.strictEqual(end0, true); assert.strictEqual(end1, true); assert.strictEqual(end2, true); }); ================================================ FILE: test/transition/tween-test.js ================================================ import assert from "assert"; import {easeCubic} from "d3-ease"; import {select, selectAll} from "d3-selection"; import {now, timeout} from "d3-timer"; import "../../src/index.js"; import {ENDING} from "../../src/transition/schedule.js"; import it from "../jsdom.js"; it("transition.tween(name, value) defines an tween using the interpolator returned by the specified function", async () => { const root = document.documentElement; let value; const interpolate = t => { value = t; }; select(root).transition().tween("foo", () => interpolate); await new Promise(resolve => timeout(elapsed => { assert.strictEqual(value, easeCubic(elapsed / 250)); resolve(); }, 125)); }); it("transition.tween(name, value) invokes the value function with the expected context and arguments", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const result = []; selectAll([one, two]).data(["one", "two"]).transition().tween("foo", function(d, i, nodes) { result.push([d, i, nodes, this]); }); await new Promise(resolve => timeout(resolve)); assert.deepStrictEqual(result, [ ["one", 0, [one, two], one], ["two", 1, [one, two], two] ]); }); it("transition.tween(name, value) passes the eased time to the interpolator", async () => { const root = document.documentElement; const then = now(); const duration = 250; const ease = easeCubic; const t = select(root).transition().tween("foo", () => interpolate); const schedule = root.__transition[t._id]; function interpolate(t) { assert.strictEqual(this, root); assert.strictEqual(t, schedule.state === ENDING ? 1 : ease((now() - then) / duration)); } await t.end(); }); it("transition.tween(name, value) allows the specified function to return null for a noop", async () => { const root = document.documentElement; const s = select(root); s.transition().tween("foo", () => {}); }); it("transition.tween(name, value) uses copy-on-write to apply changes", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const foo = () => {}; const bar = () => {}; const t = selectAll([one, two]).transition(); const schedule1 = one.__transition[t._id]; const schedule2 = two.__transition[t._id]; t.tween("foo", foo); assert.deepStrictEqual(schedule1.tween, [{name: "foo", value: foo}]); assert.strictEqual(schedule2.tween, schedule1.tween); t.tween("foo", bar); assert.deepStrictEqual(schedule1.tween, [{name: "foo", value: bar}]); assert.strictEqual(schedule2.tween, schedule1.tween); select(two).transition(t).tween("foo", foo); assert.deepStrictEqual(schedule1.tween, [{name: "foo", value: bar}]); assert.deepStrictEqual(schedule2.tween, [{name: "foo", value: foo}]); }); it("transition.tween(name, value) uses copy-on-write to apply removals", "

", async () => { const one = document.querySelector("#one"); const two = document.querySelector("#two"); const foo = () => {}; const t = selectAll([one, two]).transition(); const schedule1 = one.__transition[t._id]; const schedule2 = two.__transition[t._id]; t.tween("foo", foo); assert.deepStrictEqual(schedule1.tween, [{name: "foo", value: foo}]); assert.strictEqual(schedule2.tween, schedule1.tween); t.tween("bar", null); assert.deepStrictEqual(schedule1.tween, [{name: "foo", value: foo}]); assert.strictEqual(schedule2.tween, schedule1.tween); t.tween("foo", null); assert.deepStrictEqual(schedule1.tween, []); assert.strictEqual(schedule2.tween, schedule1.tween); select(two).transition(t).tween("foo", foo); assert.deepStrictEqual(schedule1.tween, []); assert.deepStrictEqual(schedule2.tween, [{name: "foo", value: foo}]); }); it("transition.tween(name, value) coerces the specified name to a string", async () => { const root = document.documentElement; const tween = () => {}; const t = select(root).transition().tween({toString() { return "foo"; }}, tween); assert.strictEqual(t.tween("foo"), tween); }); it("transition.tween(name) coerces the specified name to a string", async () => { const root = document.documentElement; const tween = () => {}; const t = select(root).transition().tween("foo", tween); assert.strictEqual(t.tween({toString() { return "foo"; }}), tween); }); it("transition.tween(name, value) throws an error if value is not null and not a function", async () => { const root = document.documentElement; const t = select(root).transition(); assert.throws(() => { t.tween("foo", 42); }); }); it("transition.tween(name, null) removes the specified tween", async () => { const root = document.documentElement; let frames = 0; const interpolate = () => { ++frames; }; const t = select(root).transition().tween("foo", () => interpolate).tween("foo", null); assert.strictEqual(t.tween("foo"), null); await new Promise(resolve => timeout(() => { assert.strictEqual(frames, 0); resolve(); }, 125)); }); it("transition.tween(name) returns the tween with the specified name", async () => { const root = document.documentElement; const tween = () => {}; const started = () => { assert.strictEqual(t.tween("foo"), tween); } const ended = () => { assert.strictEqual(t.tween("foo"), tween); } const t = select(root).transition().tween("foo", tween).on("start", started).on("end", ended); assert.strictEqual(t.tween("foo"), tween); assert.strictEqual(t.tween("bar"), null); await t.end(); });