diff --git a/src/channel.js b/src/channel.js index 3a6b468c86..e5b8460d98 100644 --- a/src/channel.js +++ b/src/channel.js @@ -15,10 +15,10 @@ export function Channel(data, {scale, type, value, filter, hint}) { }; } -export function channelObject(channelDescriptors, data) { +export function Channels(channelDescriptors, data) { const channels = {}; - for (const channel of channelDescriptors) { - channels[channel.name] = Channel(data, channel); + for (const name in channelDescriptors) { + channels[name] = Channel(data, channelDescriptors[name]); } return channels; } diff --git a/src/marks/dot.js b/src/marks/dot.js index d57b8c6559..82a174430b 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.js @@ -1,9 +1,9 @@ -import {path, symbolCircle} from "d3"; +import {path, select, symbolCircle} from "d3"; import {create} from "../context.js"; import {positive} from "../defined.js"; import {identity, maybeFrameAnchor, maybeNumberChannel, maybeTuple} from "../options.js"; import {Mark} from "../plot.js"; -import {applyChannelStyles, applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform} from "../style.js"; +import {applyAttr, applyChannelStyles, applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform} from "../style.js"; import {maybeSymbolChannel} from "../symbols.js"; import {sort} from "../transforms/basic.js"; import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js"; @@ -42,10 +42,9 @@ export class Dot extends Mark { // appropriate default symbols based on whether the dots are filled or // stroked, and for the symbol legend to match the appearance of the dots. const {channels} = this; - const symbolChannel = channels.find(({scale}) => scale === "symbol"); + const {symbol: symbolChannel} = channels; if (symbolChannel) { - const fillChannel = channels.find(({name}) => name === "fill"); - const strokeChannel = channels.find(({name}) => name === "stroke"); + const {fill: fillChannel, stroke: strokeChannel} = channels; symbolChannel.hint = { fill: fillChannel ? (fillChannel.value === symbolChannel.value ? "color" : "currentColor") : this.fill, stroke: strokeChannel ? (strokeChannel.value === symbolChannel.value ? "color" : "currentColor") : this.stroke @@ -89,6 +88,44 @@ export class Dot extends Mark { .call(applyChannelStyles, this, channels)) .node(); } + // TODO Support symbols. + // TODO Support other things being changed besides x and y channels. + // TODO Memoize the selection for faster updates? + // TODO Access to old channels as well as new channels. + renderUpdate(g, index, scales, channels) { + const {x: X, y: Y} = channels; + select(g).selectChildren() + .call(applyAttr, "cx", X && (i => X[i])) + .call(applyAttr, "cy", Y && (i => Y[i])); + } + renderAnimation(g, index, scales, channels, timing) { + const {x: X, y: Y} = channels; + const finishes = []; + const mark = this; + select(g).selectChildren().each(function(i) { + const animation = this.animate( + [{cx: X ? X[i] : undefined, cy: Y ? Y[i] : undefined}], + // TODO Should this be mark.data here (which is not arrayify’d), or + // should it be the transformed data that is in stateByMark, which would + // need to be passed-in to this function, perhaps as a “data” channel? + typeof timing === "function" ? timing(mark.data[i], i) : timing + ); + // Per the spec: “Authors are discouraged from using fill modes to produce + // animations whose effect is applied indefinitely… [Fill modes] produce + // situations where animation state would be accumulated indefinitely + // necessitating the automatic removal of animations defined in §5.5 + // Replacing animations. Furthermore, indefinitely filling animations can + // cause changes to specified style to be ineffective long after all + // animations have completed since the animation style takes precedence in + // the CSS cascade [css-cascade-3].” + // https://drafts.csswg.org/web-animations-1/#example-515a2006 + finishes.push(animation.finished.then(() => { + if (X) this.setAttribute("cx", X[i]); + if (Y) this.setAttribute("cy", Y[i]); + })); + }); + return Promise.all(finishes); + } } export function dot(data, {x, y, ...options} = {}) { diff --git a/src/plot.js b/src/plot.js index c34571fb50..9448fc1df0 100644 --- a/src/plot.js +++ b/src/plot.js @@ -1,6 +1,6 @@ import {cross, difference, groups, InternMap, select} from "d3"; import {Axes, autoAxisTicks, autoScaleLabels} from "./axes.js"; -import {Channel, channelObject, channelDomain, valueObject} from "./channel.js"; +import {Channel, Channels, channelDomain, valueObject} from "./channel.js"; import {Context, create} from "./context.js"; import {defined} from "./defined.js"; import {Dimensions} from "./dimensions.js"; @@ -198,6 +198,9 @@ export function plot(options = {}) { context )); } + for (const state of stateByMark.values()) { + state.nodes = new Array(facets.length); + } selection.selectAll() .data(facetKeys(scales).filter(indexByFacet.has, indexByFacet)) .enter() @@ -206,16 +209,20 @@ export function plot(options = {}) { .attr("transform", facetTranslate(fx, fy)) .each(function(key) { const j = indexByFacet.get(key); - for (const [mark, {channels, values, facets}] of stateByMark) { + for (const [mark, state] of stateByMark) { + const {channels, values, facets} = state; const facet = facets ? mark.filter(facets[j] ?? facets[0], channels, values) : null; const node = mark.render(facet, scales, values, subdimensions, context); + state.nodes[j] = node; if (node != null) this.appendChild(node); } }); } else { - for (const [mark, {channels, values, facets}] of stateByMark) { + for (const [mark, state] of stateByMark) { + const {channels, values, facets} = state; const facet = facets ? mark.filter(facets[0], channels, values) : null; const node = mark.render(facet, scales, values, dimensions, context); + state.nodes = [node]; if (node != null) svg.appendChild(node); } } @@ -239,6 +246,33 @@ export function plot(options = {}) { figure.scale = exposeScales(scaleDescriptors); figure.legend = exposeLegends(scaleDescriptors, context, options); + // TODO Combine multiple updates. + // TODO Update scale domains and axes. + // TODO Apply valueof for channel values expressed as accessors. + // TODO Reuse an existing array when instantiating channel values. + // TODO Re-apply transforms and initializers? + // TODO Update mark state. + // TODO If mark.update returns a node, replace the old one? + figure.replot = ({mark, data, animation, ...options}) => { + const {facets, nodes} = stateByMark.get(mark); + const channels = {}; + for (const name in options) { + const channel = mark.channels[name]; + if (!channel) throw new Error(`missing channel: ${name}`); + channels[name] = {value: options[name], scale: channel.scale}; + } + const values = valueObject(channels, scales); + const promises = []; + for (let i = 0, n = facets.length; i < n; ++i) { + if (animation === undefined) { + mark.renderUpdate(nodes[i], facets[i], scales, values); + } else { + promises.push(mark.renderAnimation(nodes[i], facets[i], scales, values, animation)); + } + } + return Promise.all(promises); + }; + const w = consumeWarnings(); if (w > 0) { select(svg).append("text") @@ -266,7 +300,7 @@ export class Mark { this.facet = facet == null || facet === false ? null : keyword(facet === true ? "include" : facet, "facet", ["auto", "include", "exclude"]); if (extraChannels !== undefined) channels = [...channels, ...extraChannels.filter(e => !channels.some(c => c.name === e.name))]; if (defaults !== undefined) channels = [...channels, ...styles(this, options, defaults)]; - this.channels = channels.filter(channel => { + this.channels = Object.fromEntries(channels.filter(channel => { const {name, value, optional} = channel; if (value == null) { if (optional) return false; @@ -278,7 +312,7 @@ export class Mark { if (names.has(key)) throw new Error(`duplicate channel: ${key}`); names.add(key); return true; - }); + }).map(channel => [channel.name, channel])); this.dx = +dx || 0; this.dy = +dy || 0; this.clip = maybeClip(clip); @@ -287,7 +321,7 @@ export class Mark { let data = arrayify(this.data); if (facets === undefined && data != null) facets = [range(data)]; if (this.transform != null) ({facets, data} = this.transform(data, facets)), data = arrayify(data); - const channels = channelObject(this.channels, data); + const channels = Channels(this.channels, data); if (this.sort != null) channelDomain(channels, facetChannels, data, this.sort); return {data, facets, channels}; } diff --git a/test/jsdom.js b/test/jsdom.js index 94e9193c25..50554cdd51 100644 --- a/test/jsdom.js +++ b/test/jsdom.js @@ -24,6 +24,8 @@ function withJsdom(run) { global.Node = jsdom.window.Node; global.NodeList = jsdom.window.NodeList; global.HTMLCollection = jsdom.window.HTMLCollection; + global.requestAnimationFrame = setImmediate; + global.cancelAnimationFrame = clearImmediate; global.fetch = async (href) => new Response(path.resolve("./test", href)); try { return await run(); @@ -35,6 +37,8 @@ function withJsdom(run) { delete global.Node; delete global.NodeList; delete global.HTMLCollection; + delete global.requestAnimationFrame; + delete global.cancelAnimationFrame; delete global.fetch; } }; diff --git a/test/marks/area-test.js b/test/marks/area-test.js index ed765446e7..934a8dbabb 100644 --- a/test/marks/area-test.js +++ b/test/marks/area-test.js @@ -6,9 +6,9 @@ it("area(data, options) has the expected defaults", () => { const area = Plot.area(undefined, {x1: "0", y1: "1"}); assert.strictEqual(area.data, undefined); // assert.strictEqual(area.transform, undefined); - assert.deepStrictEqual(area.channels.map(c => c.name), ["x1", "y1"]); - assert.deepStrictEqual(area.channels.map(c => c.value), ["0", "1"]); - assert.deepStrictEqual(area.channels.map(c => c.scale), ["x", "y"]); + assert.deepStrictEqual(Object.keys(area.channels), ["x1", "y1"]); + assert.deepStrictEqual(Object.values(area.channels).map(c => c.value), ["0", "1"]); + assert.deepStrictEqual(Object.values(area.channels).map(c => c.scale), ["x", "y"]); assert.strictEqual(area.curve, curveLinear); assert.strictEqual(area.fill, undefined); assert.strictEqual(area.fillOpacity, undefined); @@ -26,28 +26,28 @@ it("area(data, options) has the expected defaults", () => { it("area(data, {x1, y1, y2}) specifies an optional y2 channel", () => { const area = Plot.area(undefined, {x1: "0", y1: "1", y2: "2"}); - const y2 = area.channels.find(c => c.name === "y2"); + const {y2} = area.channels; assert.strictEqual(y2.value, "2"); assert.strictEqual(y2.scale, "y"); }); it("area(data, {x1, x2, y1}) specifies an optional x2 channel", () => { const area = Plot.area(undefined, {x1: "0", x2: "1", y1: "2"}); - const x2 = area.channels.find(c => c.name === "x2"); + const {x2} = area.channels; assert.strictEqual(x2.value, "1"); assert.strictEqual(x2.scale, "x"); }); it("area(data, {z}) specifies an optional z channel", () => { const area = Plot.area(undefined, {x1: "0", y1: "1", z: "2"}); - const z = area.channels.find(c => c.name === "z"); + const {z} = area.channels; assert.strictEqual(z.value, "2"); assert.strictEqual(z.scale, undefined); }); it("area(data, {title}) specifies an optional title channel", () => { const area = Plot.area(undefined, {x1: "0", y1: "1", title: "2"}); - const title = area.channels.find(c => c.name === "title"); + const {title} = area.channels; assert.strictEqual(title.value, "2"); assert.strictEqual(title.scale, undefined); }); @@ -65,14 +65,14 @@ it("area(data, {fill}) allows fill to be null", () => { it("area(data, {fill}) allows fill to be a variable color", () => { const area = Plot.area(undefined, {x1: "0", y1: "1", fill: "x"}); assert.strictEqual(area.fill, undefined); - const fill = area.channels.find(c => c.name === "fill"); + const {fill} = area.channels; assert.strictEqual(fill.value, "x"); assert.strictEqual(fill.scale, "color"); }); it("area(data, {fill}) implies a default z channel if fill is variable", () => { const area = Plot.area(undefined, {x1: "0", y1: "1", fill: "2", stroke: "3"}); // fill takes priority - const z = area.channels.find(c => c.name === "z"); + const {z} = area.channels; assert.strictEqual(z.value, "2"); assert.strictEqual(z.scale, undefined); }); @@ -90,14 +90,14 @@ it("area(data, {stroke}) allows stroke to be null", () => { it("area(data, {stroke}) allows stroke to be a variable color", () => { const area = Plot.area(undefined, {x1: "0", y1: "1", stroke: "x"}); assert.strictEqual(area.stroke, undefined); - const stroke = area.channels.find(c => c.name === "stroke"); + const {stroke} = area.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); it("area(data, {stroke}) implies a default z channel if stroke is variable", () => { const area = Plot.area(undefined, {x1: "0", y1: "1", stroke: "2"}); - const z = area.channels.find(c => c.name === "z"); + const {z} = area.channels; assert.strictEqual(z.value, "2"); assert.strictEqual(z.scale, undefined); }); @@ -109,26 +109,26 @@ it("area(data, {curve}) specifies a named curve or function", () => { it("areaX(data, {x, y}) defaults x1 to zero, x2 to x, and y1 to y", () => { const area = Plot.areaX(undefined, {x: "0", y: "1"}); - const x1 = area.channels.find(c => c.name === "x1"); + const {x1} = area.channels; // assert.strictEqual(x1.value, 0); assert.strictEqual(x1.scale, "x"); - const x2 = area.channels.find(c => c.name === "x2"); + const {x2} = area.channels; assert.strictEqual(x2.value.label, "0"); assert.strictEqual(x2.scale, "x"); - const y1 = area.channels.find(c => c.name === "y1"); + const {y1} = area.channels; assert.strictEqual(y1.value, "1"); assert.strictEqual(y1.scale, "y"); }); it("areaY(data, {x, y}) defaults x1 to x, y1 to zero, and y2 to y", () => { const area = Plot.areaY(undefined, {x: "0", y: "1"}); - const x1 = area.channels.find(c => c.name === "x1"); + const {x1} = area.channels; assert.strictEqual(x1.value, "0"); assert.strictEqual(x1.scale, "x"); - const y1 = area.channels.find(c => c.name === "y1"); + const {y1} = area.channels; // assert.strictEqual(y1.value, 0); assert.strictEqual(y1.scale, "y"); - const y2 = area.channels.find(c => c.name === "y2"); + const {y2} = area.channels; assert.strictEqual(y2.value.label, "1"); assert.strictEqual(y2.scale, "y"); }); diff --git a/test/marks/bar-test.js b/test/marks/bar-test.js index 55e3c43bbf..0e92350c2a 100644 --- a/test/marks/bar-test.js +++ b/test/marks/bar-test.js @@ -5,9 +5,9 @@ it("barX() has the expected defaults", () => { const bar = Plot.barX(); assert.strictEqual(bar.data, undefined); // assert.strictEqual(bar.transform, undefined); - assert.deepStrictEqual(bar.channels.map(c => c.name), ["x1", "x2", "y"]); - // assert.deepStrictEqual(bar.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[0, 0, 0], [1, 2, 3]]); - assert.deepStrictEqual(bar.channels.map(c => c.scale), ["x", "x", "y"]); + assert.deepStrictEqual(Object.keys(bar.channels), ["x1", "x2", "y"]); + // assert.deepStrictEqual(Object.values(bar.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[0, 0, 0], [1, 2, 3]]); + assert.deepStrictEqual(Object.values(bar.channels).map(c => c.scale), ["x", "x", "y"]); assert.strictEqual(bar.fill, undefined); assert.strictEqual(bar.fillOpacity, undefined); assert.strictEqual(bar.stroke, undefined); @@ -28,15 +28,15 @@ it("barX() has the expected defaults", () => { it("barX(data, {y}) uses a band scale", () => { const bar = Plot.barX(undefined, {y: "x"}); - assert.deepStrictEqual(bar.channels.map(c => c.name), ["x1", "x2", "y"]); - assert.deepStrictEqual(bar.channels.map(c => c.scale), ["x", "x", "y"]); - assert.strictEqual(bar.channels.find(c => c.name === "y").type, "band"); - assert.strictEqual(bar.channels.find(c => c.name === "y").value.label, "x"); + assert.deepStrictEqual(Object.keys(bar.channels), ["x1", "x2", "y"]); + assert.deepStrictEqual(Object.values(bar.channels).map(c => c.scale), ["x", "x", "y"]); + assert.strictEqual(bar.channels.y.type, "band"); + assert.strictEqual(bar.channels.y.value.label, "x"); }); it("barX(data, {title}) specifies an optional title channel", () => { const bar = Plot.barX(undefined, {title: "x"}); - const title = bar.channels.find(c => c.name === "title"); + const {title} = bar.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -54,7 +54,7 @@ it("barX(data, {fill}) allows fill to be null", () => { it("barX(data, {fill}) allows fill to be a variable color", () => { const bar = Plot.barX(undefined, {fill: "x"}); assert.strictEqual(bar.fill, undefined); - const fill = bar.channels.find(c => c.name === "fill"); + const {fill} = bar.channels; assert.strictEqual(fill.value, "x"); assert.strictEqual(fill.scale, "color"); }); @@ -72,20 +72,20 @@ it("barX(data, {stroke}) allows stroke to be null", () => { it("barX(data, {stroke}) allows stroke to be a variable color", () => { const bar = Plot.barX(undefined, {stroke: "x"}); assert.strictEqual(bar.stroke, undefined); - const stroke = bar.channels.find(c => c.name === "stroke"); + const {stroke} = bar.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); it("barX(data, {x, y}) defaults x1 to zero and x2 to x", () => { const bar = Plot.barX(undefined, {x: "0", y: "1"}); - const x1 = bar.channels.find(c => c.name === "x1"); + const {x1} = bar.channels; // assert.strictEqual(x1.value, 0); assert.strictEqual(x1.scale, "x"); - const x2 = bar.channels.find(c => c.name === "x2"); + const {x2} = bar.channels; assert.strictEqual(x2.value.label, "0"); assert.strictEqual(x2.scale, "x"); - const y = bar.channels.find(c => c.name === "y"); + const {y} = bar.channels; assert.strictEqual(y.value.label, "1"); assert.strictEqual(y.scale, "y"); }); @@ -99,9 +99,9 @@ it("barY() has the expected defaults", () => { const bar = Plot.barY(); assert.strictEqual(bar.data, undefined); // assert.strictEqual(bar.transform, undefined); - assert.deepStrictEqual(bar.channels.map(c => c.name), ["y1", "y2", "x"]); - // assert.deepStrictEqual(bar.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[0, 0, 0], [1, 2, 3]]); - assert.deepStrictEqual(bar.channels.map(c => c.scale), ["y", "y", "x"]); + assert.deepStrictEqual(Object.keys(bar.channels), ["y1", "y2", "x"]); + // assert.deepStrictEqual(Object.values(bar.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[0, 0, 0], [1, 2, 3]]); + assert.deepStrictEqual(Object.values(bar.channels).map(c => c.scale), ["y", "y", "x"]); assert.strictEqual(bar.fill, undefined); assert.strictEqual(bar.fillOpacity, undefined); assert.strictEqual(bar.stroke, undefined); @@ -122,15 +122,15 @@ it("barY() has the expected defaults", () => { it("barY(data, {x}) uses a band scale", () => { const bar = Plot.barY(undefined, {x: "y"}); - assert.deepStrictEqual(bar.channels.map(c => c.name), ["y1", "y2", "x"]); - assert.deepStrictEqual(bar.channels.map(c => c.scale), ["y", "y", "x"]); - assert.strictEqual(bar.channels.find(c => c.name === "x").type, "band"); - assert.strictEqual(bar.channels.find(c => c.name === "x").value.label, "y"); + assert.deepStrictEqual(Object.keys(bar.channels), ["y1", "y2", "x"]); + assert.deepStrictEqual(Object.values(bar.channels).map(c => c.scale), ["y", "y", "x"]); + assert.strictEqual(bar.channels.x.type, "band"); + assert.strictEqual(bar.channels.x.value.label, "y"); }); it("barY(data, {title}) specifies an optional title channel", () => { const bar = Plot.barY(undefined, {title: "x"}); - const title = bar.channels.find(c => c.name === "title"); + const {title} = bar.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -148,7 +148,7 @@ it("barY(data, {fill}) allows fill to be null", () => { it("barY(data, {fill}) allows fill to be a variable color", () => { const bar = Plot.barY(undefined, {fill: "x"}); assert.strictEqual(bar.fill, undefined); - const fill = bar.channels.find(c => c.name === "fill"); + const {fill} = bar.channels; assert.strictEqual(fill.value, "x"); assert.strictEqual(fill.scale, "color"); }); @@ -166,20 +166,20 @@ it("barY(data, {stroke}) allows stroke to be null", () => { it("barY(data, {stroke}) allows stroke to be a variable color", () => { const bar = Plot.barY(undefined, {stroke: "x"}); assert.strictEqual(bar.stroke, undefined); - const stroke = bar.channels.find(c => c.name === "stroke"); + const {stroke} = bar.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); it("barY(data, {x, y}) defaults y1 to zero and y2 to y", () => { const bar = Plot.barY(undefined, {x: "0", y: "1"}); - const x = bar.channels.find(c => c.name === "x"); + const {x} = bar.channels; assert.strictEqual(x.value.label, "0"); assert.strictEqual(x.scale, "x"); - const y1 = bar.channels.find(c => c.name === "y1"); + const {y1} = bar.channels; // assert.strictEqual(y1.value, 0); assert.strictEqual(y1.scale, "y"); - const y2 = bar.channels.find(c => c.name === "y2"); + const {y2} = bar.channels; assert.strictEqual(y2.value.label, "1"); assert.strictEqual(y2.scale, "y"); }); diff --git a/test/marks/cell-test.js b/test/marks/cell-test.js index f5363cb4cd..978d6d9d6e 100644 --- a/test/marks/cell-test.js +++ b/test/marks/cell-test.js @@ -5,11 +5,11 @@ it("cell() has the expected defaults", () => { const cell = Plot.cell(); assert.strictEqual(cell.data, undefined); assert.strictEqual(cell.transform, undefined); - assert.deepStrictEqual(cell.channels.map(c => c.name), ["x", "y"]); - assert.deepStrictEqual(cell.channels.map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4]]); - assert.deepStrictEqual(cell.channels.map(c => c.scale), ["x", "y"]); - assert.strictEqual(cell.channels.find(c => c.name === "x").type, "band"); - assert.strictEqual(cell.channels.find(c => c.name === "y").type, "band"); + assert.deepStrictEqual(Object.keys(cell.channels), ["x", "y"]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4]]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => c.scale), ["x", "y"]); + assert.strictEqual(cell.channels.x.type, "band"); + assert.strictEqual(cell.channels.y.type, "band"); assert.strictEqual(cell.fill, undefined); assert.strictEqual(cell.fillOpacity, undefined); assert.strictEqual(cell.stroke, undefined); @@ -30,7 +30,7 @@ it("cell() has the expected defaults", () => { it("cell(data, {title}) specifies an optional title channel", () => { const cell = Plot.cell(undefined, {title: "x"}); - const title = cell.channels.find(c => c.name === "title"); + const {title} = cell.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -48,7 +48,7 @@ it("cell(data, {fill}) allows fill to be null", () => { it("cell(data, {fill}) allows fill to be a variable color", () => { const cell = Plot.cell(undefined, {fill: "x"}); assert.strictEqual(cell.fill, undefined); - const fill = cell.channels.find(c => c.name === "fill"); + const {fill} = cell.channels; assert.strictEqual(fill.value, "x"); assert.strictEqual(fill.scale, "color"); }); @@ -66,7 +66,7 @@ it("cell(data, {stroke}) allows stroke to be null", () => { it("cell(data, {stroke}) allows stroke to be a variable color", () => { const cell = Plot.cell(undefined, {stroke: "x"}); assert.strictEqual(cell.stroke, undefined); - const stroke = cell.channels.find(c => c.name === "stroke"); + const {stroke} = cell.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); @@ -75,18 +75,18 @@ it("cellX() defaults x to identity and y to null", () => { const cell = Plot.cellX(); assert.strictEqual(cell.data, undefined); assert.strictEqual(cell.transform, undefined); - assert.deepStrictEqual(cell.channels.map(c => c.name), ["x", "fill"]); - assert.deepStrictEqual(cell.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[ 0, 1, 2 ], [ 1, 2, 3 ]]); - assert.deepStrictEqual(cell.channels.map(c => c.scale), ["x", "color"]); - assert.strictEqual(cell.channels.find(c => c.name === "x").type, "band"); + assert.deepStrictEqual(Object.keys(cell.channels), ["x", "fill"]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[ 0, 1, 2 ], [ 1, 2, 3 ]]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => c.scale), ["x", "color"]); + assert.strictEqual(cell.channels.x.type, "band"); }); it("cellY() defaults y to identity and x to null", () => { const cell = Plot.cellY(); assert.strictEqual(cell.data, undefined); assert.strictEqual(cell.transform, undefined); - assert.deepStrictEqual(cell.channels.map(c => c.name), ["y", "fill"]); - assert.deepStrictEqual(cell.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[ 0, 1, 2 ], [ 1, 2, 3 ]]); - assert.deepStrictEqual(cell.channels.map(c => c.scale), ["y", "color"]); - assert.strictEqual(cell.channels.find(c => c.name === "y").type, "band"); + assert.deepStrictEqual(Object.keys(cell.channels), ["y", "fill"]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[ 0, 1, 2 ], [ 1, 2, 3 ]]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => c.scale), ["y", "color"]); + assert.strictEqual(cell.channels.y.type, "band"); }); diff --git a/test/marks/dot-test.js b/test/marks/dot-test.js index b1508b8b50..1b2dd96df8 100644 --- a/test/marks/dot-test.js +++ b/test/marks/dot-test.js @@ -5,9 +5,9 @@ it("dot() has the expected defaults", () => { const dot = Plot.dot(); assert.strictEqual(dot.data, undefined); assert.strictEqual(dot.transform, undefined); - assert.deepStrictEqual(dot.channels.map(c => c.name), ["x", "y"]); - assert.deepStrictEqual(dot.channels.map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4]]); - assert.deepStrictEqual(dot.channels.map(c => c.scale), ["x", "y"]); + assert.deepStrictEqual(Object.keys(dot.channels), ["x", "y"]); + assert.deepStrictEqual(Object.values(dot.channels).map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4]]); + assert.deepStrictEqual(Object.values(dot.channels).map(c => c.scale), ["x", "y"]); assert.strictEqual(dot.r, 3); assert.strictEqual(dot.fill, "none"); assert.strictEqual(dot.fillOpacity, undefined); @@ -35,14 +35,14 @@ it("dot(data, {r}) allows r to be a constant radius", () => { it("dot(data, {r}) allows r to be a variable radius", () => { const dot = Plot.dot(undefined, {r: "x"}); assert.strictEqual(dot.r, undefined); - const r = dot.channels.find(c => c.name === "r"); + const {r} = dot.channels; assert.strictEqual(r.value, "x"); assert.strictEqual(r.scale, "r"); }); it("dot(data, {title}) specifies an optional title channel", () => { const dot = Plot.dot(undefined, {title: "x"}); - const title = dot.channels.find(c => c.name === "title"); + const {title} = dot.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -60,7 +60,7 @@ it("dot(data, {fill}) allows fill to be null", () => { it("dot(data, {fill}) allows fill to be a variable color", () => { const dot = Plot.dot(undefined, {fill: "x"}); assert.strictEqual(dot.fill, undefined); - const fill = dot.channels.find(c => c.name === "fill"); + const {fill} = dot.channels; assert.strictEqual(fill.value, "x"); assert.strictEqual(fill.scale, "color"); }); @@ -84,7 +84,7 @@ it("dot(data, {stroke}) allows stroke to be null", () => { it("dot(data, {stroke}) allows stroke to be a variable color", () => { const dot = Plot.dot(undefined, {stroke: "x"}); assert.strictEqual(dot.stroke, undefined); - const stroke = dot.channels.find(c => c.name === "stroke"); + const {stroke} = dot.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); diff --git a/test/marks/frame-test.js b/test/marks/frame-test.js index b749eebce0..2eb6483c34 100644 --- a/test/marks/frame-test.js +++ b/test/marks/frame-test.js @@ -5,7 +5,7 @@ it("frame(options) has the expected defaults", () => { const frame = Plot.frame(); assert.strictEqual(frame.data, undefined); assert.strictEqual(frame.transform, undefined); - assert.deepStrictEqual(frame.channels, []); + assert.deepStrictEqual(frame.channels, {}); assert.strictEqual(frame.fill, "none"); assert.strictEqual(frame.fillOpacity, undefined); assert.strictEqual(frame.stroke, "currentColor"); diff --git a/test/marks/image-test.js b/test/marks/image-test.js index 2a9f51dfd2..21e57e5c85 100644 --- a/test/marks/image-test.js +++ b/test/marks/image-test.js @@ -5,9 +5,9 @@ it("image(undefined, {src}) has the expected defaults", () => { const image = Plot.image(undefined, {src: "foo"}); assert.strictEqual(image.data, undefined); assert.strictEqual(image.transform, undefined); - assert.deepStrictEqual(image.channels.map(c => c.name), ["x", "y", "src"]); - assert.deepStrictEqual(image.channels.map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4], [undefined, undefined]]); - assert.deepStrictEqual(image.channels.map(c => c.scale), ["x", "y", undefined]); + assert.deepStrictEqual(Object.keys(image.channels), ["x", "y", "src"]); + assert.deepStrictEqual(Object.values(image.channels).map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4], [undefined, undefined]]); + assert.deepStrictEqual(Object.values(image.channels).map(c => c.scale), ["x", "y", undefined]); assert.strictEqual(image.width, 16); assert.strictEqual(image.height, 16); assert.strictEqual(image.preserveAspectRatio, undefined); @@ -24,15 +24,15 @@ it("image(data, {width, height, src}) allows width and height to be a variable a const image = Plot.image(undefined, {width: "x", height: "y", src: "foo"}); assert.strictEqual(image.width, undefined); assert.strictEqual(image.height, undefined); - const width = image.channels.find(c => c.name === "width"); - const height = image.channels.find(c => c.name === "height"); + const {width} = image.channels; + const {height} = image.channels; assert.strictEqual(width.value, "x"); assert.strictEqual(height.value, "y"); }); it("image(data, {title, src}) specifies an optional title channel", () => { const image = Plot.image(undefined, {title: "x", src: "foo"}); - const title = image.channels.find(c => c.name === "title"); + const {title} = image.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -49,7 +49,7 @@ it("image(data, {src}) allows src to be a constant", () => { it("image(data, {src}) allows src to be a channel", () => { const image = Plot.image(undefined, {src: "foo"}); - const src = image.channels.find(c => c.name === "src"); + const {src} = image.channels; assert.strictEqual(src.value, "foo"); assert.strictEqual(src.scale, undefined); }); diff --git a/test/marks/line-test.js b/test/marks/line-test.js index 855ec2f44f..d35f7b1e0a 100644 --- a/test/marks/line-test.js +++ b/test/marks/line-test.js @@ -6,9 +6,9 @@ it("line() has the expected defaults", () => { const line = Plot.line(); assert.strictEqual(line.data, undefined); assert.strictEqual(line.transform, undefined); - assert.deepStrictEqual(line.channels.map(c => c.name), ["x", "y"]); - assert.deepStrictEqual(line.channels.map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4]]); - assert.deepStrictEqual(line.channels.map(c => c.scale), ["x", "y"]); + assert.deepStrictEqual(Object.keys(line.channels), ["x", "y"]); + assert.deepStrictEqual(Object.values(line.channels).map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4]]); + assert.deepStrictEqual(Object.values(line.channels).map(c => c.scale), ["x", "y"]); assert.strictEqual(line.curve, curveLinear); assert.strictEqual(line.fill, "none"); assert.strictEqual(line.fillOpacity, undefined); @@ -26,14 +26,14 @@ it("line() has the expected defaults", () => { it("line(data, {z}) specifies an optional z channel", () => { const line = Plot.line(undefined, {z: "2"}); - const z = line.channels.find(c => c.name === "z"); + const {z} = line.channels; assert.strictEqual(z.value, "2"); assert.strictEqual(z.scale, undefined); }); it("line(data, {title}) specifies an optional title channel", () => { const line = Plot.line(undefined, {title: "2"}); - const title = line.channels.find(c => c.name === "title"); + const {title} = line.channels; assert.strictEqual(title.value, "2"); assert.strictEqual(title.scale, undefined); }); @@ -51,14 +51,14 @@ it("line(data, {fill}) allows fill to be null", () => { it("line(data, {fill}) allows fill to be a variable color", () => { const line = Plot.line(undefined, {fill: "x"}); assert.strictEqual(line.fill, undefined); - const fill = line.channels.find(c => c.name === "fill"); + const {fill} = line.channels; assert.strictEqual(fill.value, "x"); assert.strictEqual(fill.scale, "color"); }); it("line(data, {fill}) implies a default z channel if fill is variable", () => { const line = Plot.line(undefined, {fill: "2"}); - const z = line.channels.find(c => c.name === "z"); + const {z} = line.channels; assert.strictEqual(z.value, "2"); assert.strictEqual(z.scale, undefined); }); @@ -81,14 +81,14 @@ it("line(data, {stroke}) implies no stroke width if stroke is null", () => { it("line(data, {stroke}) allows stroke to be a variable color", () => { const line = Plot.line(undefined, {stroke: "x", fill: "3"}); // stroke takes priority assert.strictEqual(line.stroke, undefined); - const stroke = line.channels.find(c => c.name === "stroke"); + const {stroke} = line.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); it("line(data, {stroke}) implies a default z channel if stroke is variable", () => { const line = Plot.line(undefined, {stroke: "2"}); - const z = line.channels.find(c => c.name === "z"); + const {z} = line.channels; assert.strictEqual(z.value, "2"); assert.strictEqual(z.scale, undefined); }); diff --git a/test/marks/link-test.js b/test/marks/link-test.js index 3505e5b70f..8fc6c1482d 100644 --- a/test/marks/link-test.js +++ b/test/marks/link-test.js @@ -5,9 +5,9 @@ it("link(data, options) has the expected defaults", () => { const link = Plot.link(undefined, {x1: "0", y1: "1", x2: "2", y2: "3"}); assert.strictEqual(link.data, undefined); assert.strictEqual(link.transform, undefined); - assert.deepStrictEqual(link.channels.map(c => c.name), ["x1", "y1", "x2", "y2"]); - assert.deepStrictEqual(link.channels.map(c => c.value), ["0", "1", "2", "3"]); - assert.deepStrictEqual(link.channels.map(c => c.scale), ["x", "y", "x", "y"]); + assert.deepStrictEqual(Object.keys(link.channels), ["x1", "y1", "x2", "y2"]); + assert.deepStrictEqual(Object.values(link.channels).map(c => c.value), ["0", "1", "2", "3"]); + assert.deepStrictEqual(Object.values(link.channels).map(c => c.scale), ["x", "y", "x", "y"]); assert.strictEqual(link.fill, "none"); assert.strictEqual(link.fillOpacity, undefined); assert.strictEqual(link.stroke, "currentColor"); @@ -24,7 +24,7 @@ it("link(data, options) has the expected defaults", () => { it("link(data, {title}) specifies an optional title channel", () => { const link = Plot.link(undefined, {x1: "0", y1: "1", x2: "2", y2: "3", title: "4"}); - const title = link.channels.find(c => c.name === "title"); + const {title} = link.channels; assert.strictEqual(title.value, "4"); assert.strictEqual(title.scale, undefined); }); @@ -42,7 +42,7 @@ it("link(data, {stroke}) allows stroke to be null", () => { it("link(data, {stroke}) allows stroke to be a variable color", () => { const link = Plot.link(undefined, {x1: "0", y1: "1", x2: "2", y2: "3", stroke: "4"}); assert.strictEqual(link.stroke, undefined); - const stroke = link.channels.find(c => c.name === "stroke"); + const {stroke} = link.channels; assert.strictEqual(stroke.value, "4"); assert.strictEqual(stroke.scale, "color"); }); diff --git a/test/marks/rect-test.js b/test/marks/rect-test.js index c66732aef1..7c06f7836a 100644 --- a/test/marks/rect-test.js +++ b/test/marks/rect-test.js @@ -5,9 +5,9 @@ it("rect(data, options) has the expected defaults", () => { const rect = Plot.rect(undefined, {x1: "0", y1: "1", x2: "2", y2: "3"}); assert.strictEqual(rect.data, undefined); assert.strictEqual(rect.transform, undefined); - assert.deepStrictEqual(rect.channels.map(c => c.name), ["x1", "y1", "x2", "y2"]); - assert.deepStrictEqual(rect.channels.map(c => c.value), ["0", "1", "2", "3"]); - assert.deepStrictEqual(rect.channels.map(c => c.scale), ["x", "y", "x", "y"]); + assert.deepStrictEqual(Object.keys(rect.channels), ["x1", "y1", "x2", "y2"]); + assert.deepStrictEqual(Object.values(rect.channels).map(c => c.value), ["0", "1", "2", "3"]); + assert.deepStrictEqual(Object.values(rect.channels).map(c => c.scale), ["x", "y", "x", "y"]); assert.strictEqual(rect.fill, undefined); assert.strictEqual(rect.fillOpacity, undefined); assert.strictEqual(rect.stroke, undefined); @@ -28,7 +28,7 @@ it("rect(data, options) has the expected defaults", () => { it("rect(data, {title}) specifies an optional title channel", () => { const rect = Plot.rect(undefined, {x1: "0", y1: "1", x2: "2", y2: "3", title: "4"}); - const title = rect.channels.find(c => c.name === "title"); + const {title} = rect.channels; assert.strictEqual(title.value, "4"); assert.strictEqual(title.scale, undefined); }); @@ -46,7 +46,7 @@ it("rect(data, {fill}) allows fill to be null", () => { it("rect(data, {fill}) allows fill to be a variable color", () => { const rect = Plot.rect(undefined, {x1: "0", y1: "1", x2: "2", y2: "3", fill: "4"}); assert.strictEqual(rect.fill, undefined); - const fill = rect.channels.find(c => c.name === "fill"); + const {fill} = rect.channels; assert.strictEqual(fill.value, "4"); assert.strictEqual(fill.scale, "color"); }); @@ -64,7 +64,7 @@ it("rect(data, {stroke}) allows stroke to be null", () => { it("rect(data, {stroke}) allows stroke to be a variable color", () => { const rect = Plot.rect(undefined, {x1: "0", y1: "1", x2: "2", y2: "3", stroke: "4"}); assert.strictEqual(rect.stroke, undefined); - const stroke = rect.channels.find(c => c.name === "stroke"); + const {stroke} = rect.channels; assert.strictEqual(stroke.value, "4"); assert.strictEqual(stroke.scale, "color"); }); diff --git a/test/marks/rule-test.js b/test/marks/rule-test.js index 4bd0ac475e..1e96889e6c 100644 --- a/test/marks/rule-test.js +++ b/test/marks/rule-test.js @@ -5,9 +5,9 @@ it("ruleX() has the expected defaults", () => { const rule = Plot.ruleX(); assert.strictEqual(rule.data, undefined); assert.strictEqual(rule.transform, undefined); - assert.deepStrictEqual(rule.channels.map(c => c.name), ["x"]); - assert.deepStrictEqual(rule.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3]]); - assert.deepStrictEqual(rule.channels.map(c => c.scale), ["x"]); + assert.deepStrictEqual(Object.keys(rule.channels), ["x"]); + assert.deepStrictEqual(Object.values(rule.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3]]); + assert.deepStrictEqual(Object.values(rule.channels).map(c => c.scale), ["x"]); assert.strictEqual(rule.fill, undefined); assert.strictEqual(rule.fillOpacity, undefined); assert.strictEqual(rule.stroke, "currentColor"); @@ -24,7 +24,7 @@ it("ruleX() has the expected defaults", () => { it("ruleX(data, {title}) specifies an optional title channel", () => { const rule = Plot.ruleX(undefined, {title: "x"}); - const title = rule.channels.find(c => c.name === "title"); + const {title} = rule.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -42,50 +42,50 @@ it("ruleX(data, {stroke}) allows stroke to be null", () => { it("ruleX(data, {stroke}) allows stroke to be a variable color", () => { const rule = Plot.ruleX(undefined, {stroke: "x"}); assert.strictEqual(rule.stroke, undefined); - const stroke = rule.channels.find(c => c.name === "stroke"); + const {stroke} = rule.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); it("ruleX(data, {x, y}) specifies y1 = zero, y2 = y", () => { const rule = Plot.ruleX(undefined, {x: "0", y: "1"}); - const y1 = rule.channels.find(c => c.name === "y1"); + const {y1} = rule.channels; assert.strictEqual(y1.value, 0); assert.strictEqual(y1.scale, "y"); - const y2 = rule.channels.find(c => c.name === "y2"); + const {y2} = rule.channels; assert.strictEqual(y2.value, "1"); assert.strictEqual(y2.scale, "y"); }); it("ruleX(data, {x, y1}) specifies y1 = zero, y2 = y1", () => { const rule = Plot.ruleX(undefined, {x: "0", y1: "1"}); - const y1 = rule.channels.find(c => c.name === "y1"); + const {y1} = rule.channels; assert.strictEqual(y1.value, 0); assert.strictEqual(y1.scale, "y"); - const y2 = rule.channels.find(c => c.name === "y2"); + const {y2} = rule.channels; assert.strictEqual(y2.value, "1"); assert.strictEqual(y2.scale, "y"); }); it("ruleX(data, {x, y2}) specifies y1 = zero, y2 = y2", () => { const rule = Plot.ruleX(undefined, {x: "0", y2: "1"}); - const y1 = rule.channels.find(c => c.name === "y1"); + const {y1} = rule.channels; assert.strictEqual(y1.value, 0); assert.strictEqual(y1.scale, "y"); - const y2 = rule.channels.find(c => c.name === "y2"); + const {y2} = rule.channels; assert.strictEqual(y2.value, "1"); assert.strictEqual(y2.scale, "y"); }); it("ruleX(data, {x, y1, y2}) specifies x, y1, y2", () => { const rule = Plot.ruleX(undefined, {x: "0", y1: "1", y2: "2"}); - const x = rule.channels.find(c => c.name === "x"); + const {x} = rule.channels; assert.strictEqual(x.value, "0"); assert.strictEqual(x.scale, "x"); - const y1 = rule.channels.find(c => c.name === "y1"); + const {y1} = rule.channels; assert.strictEqual(y1.value, "1"); assert.strictEqual(y1.scale, "y"); - const y2 = rule.channels.find(c => c.name === "y2"); + const {y2} = rule.channels; assert.strictEqual(y2.value, "2"); assert.strictEqual(y2.scale, "y"); }); @@ -94,9 +94,9 @@ it("ruleY() has the expected defaults", () => { const rule = Plot.ruleY(); assert.strictEqual(rule.data, undefined); assert.strictEqual(rule.transform, undefined); - assert.deepStrictEqual(rule.channels.map(c => c.name), ["y"]); - assert.deepStrictEqual(rule.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3]]); - assert.deepStrictEqual(rule.channels.map(c => c.scale), ["y"]); + assert.deepStrictEqual(Object.keys(rule.channels), ["y"]); + assert.deepStrictEqual(Object.values(rule.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3]]); + assert.deepStrictEqual(Object.values(rule.channels).map(c => c.scale), ["y"]); assert.strictEqual(rule.fill, undefined); assert.strictEqual(rule.fillOpacity, undefined); assert.strictEqual(rule.stroke, "currentColor"); @@ -113,7 +113,7 @@ it("ruleY() has the expected defaults", () => { it("ruleY(data, {title}) specifies an optional title channel", () => { const rule = Plot.ruleY(undefined, {title: "x"}); - const title = rule.channels.find(c => c.name === "title"); + const {title} = rule.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -131,50 +131,50 @@ it("ruleY(data, {stroke}) allows stroke to be null", () => { it("ruleY(data, {stroke}) allows stroke to be a variable color", () => { const rule = Plot.ruleY(undefined, {stroke: "x"}); assert.strictEqual(rule.stroke, undefined); - const stroke = rule.channels.find(c => c.name === "stroke"); + const {stroke} = rule.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); it("ruleY(data, {x, y}) specifies x1 = zero, x2 = x", () => { const rule = Plot.ruleY(undefined, {x: "0", y: "1"}); - const x1 = rule.channels.find(c => c.name === "x1"); + const {x1} = rule.channels; assert.strictEqual(x1.value, 0); assert.strictEqual(x1.scale, "x"); - const x2 = rule.channels.find(c => c.name === "x2"); + const {x2} = rule.channels; assert.strictEqual(x2.value, "0"); assert.strictEqual(x2.scale, "x"); }); it("ruleY(data, {y, x1}) specifies x1 = zero, x2 = x1", () => { const rule = Plot.ruleY(undefined, {x1: "0", y: "1"}); - const x1 = rule.channels.find(c => c.name === "x1"); + const {x1} = rule.channels; assert.strictEqual(x1.value, 0); assert.strictEqual(x1.scale, "x"); - const x2 = rule.channels.find(c => c.name === "x2"); + const {x2} = rule.channels; assert.strictEqual(x2.value, "0"); assert.strictEqual(x2.scale, "x"); }); it("ruleY(data, {y, x2}) specifies x1 = zero, x2 = x2", () => { const rule = Plot.ruleY(undefined, {x2: "0", y: "1"}); - const x1 = rule.channels.find(c => c.name === "x1"); + const {x1} = rule.channels; assert.strictEqual(x1.value, 0); assert.strictEqual(x1.scale, "x"); - const x2 = rule.channels.find(c => c.name === "x2"); + const {x2} = rule.channels; assert.strictEqual(x2.value, "0"); assert.strictEqual(x2.scale, "x"); }); it("ruleY(data, {x1, x2, y}) specifies x1, x2, y", () => { const rule = Plot.ruleY(undefined, {x1: "0", x2: "1", y: "2"}); - const x1 = rule.channels.find(c => c.name === "x1"); + const {x1} = rule.channels; assert.strictEqual(x1.value, "0"); assert.strictEqual(x1.scale, "x"); - const x2 = rule.channels.find(c => c.name === "x2"); + const {x2} = rule.channels; assert.strictEqual(x2.value, "1"); assert.strictEqual(x2.scale, "x"); - const y = rule.channels.find(c => c.name === "y"); + const {y} = rule.channels; assert.strictEqual(y.value, "2"); assert.strictEqual(y.scale, "y"); }); diff --git a/test/marks/text-test.js b/test/marks/text-test.js index 1c393980fd..6b1281bfb4 100644 --- a/test/marks/text-test.js +++ b/test/marks/text-test.js @@ -5,9 +5,9 @@ it("text() has the expected defaults", () => { const text = Plot.text(); assert.strictEqual(text.data, undefined); assert.strictEqual(text.transform, undefined); - assert.deepStrictEqual(text.channels.map(c => c.name), ["x", "y", "text"]); - assert.deepStrictEqual(text.channels.map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4], [0, 1]]); - assert.deepStrictEqual(text.channels.map(c => c.scale), ["x", "y", undefined]); + assert.deepStrictEqual(Object.keys(text.channels), ["x", "y", "text"]); + assert.deepStrictEqual(Object.values(text.channels).map(c => Plot.valueof([[1, 2], [3, 4]], c.value)), [[1, 3], [2, 4], [0, 1]]); + assert.deepStrictEqual(Object.values(text.channels).map(c => c.scale), ["x", "y", undefined]); assert.strictEqual(text.fill, undefined); assert.strictEqual(text.fillOpacity, undefined); assert.strictEqual(text.stroke, undefined); @@ -33,8 +33,8 @@ it("text(strings, {frameAnchor}) has the expected defaults", () => { const text = Plot.text(data, {frameAnchor: "middle"}); assert.strictEqual(text.data, data); assert.strictEqual(text.transform, undefined); - assert.deepStrictEqual(text.channels.map(c => c.name), ["text"]); - assert.deepStrictEqual(text.channels.map(c => Plot.valueof(data, c.value)), [data]); + assert.deepStrictEqual(Object.keys(text.channels), ["text"]); + assert.deepStrictEqual(Object.values(text.channels).map(c => Plot.valueof(data, c.value)), [data]); assert.strictEqual(text.textAnchor, undefined); assert.strictEqual(text.lineAnchor, "middle"); assert.strictEqual(text.frameAnchor, "middle"); @@ -45,8 +45,8 @@ it("text(dates, {frameAnchor}) has the expected defaults", () => { const text = Plot.text(data, {frameAnchor: "middle"}); assert.strictEqual(text.data, data); assert.strictEqual(text.transform, undefined); - assert.deepStrictEqual(text.channels.map(c => c.name), ["text"]); - assert.deepStrictEqual(text.channels.map(c => Plot.valueof(data, c.value)), [data]); + assert.deepStrictEqual(Object.keys(text.channels), ["text"]); + assert.deepStrictEqual(Object.values(text.channels).map(c => Plot.valueof(data, c.value)), [data]); assert.strictEqual(text.textAnchor, undefined); assert.strictEqual(text.lineAnchor, "middle"); assert.strictEqual(text.frameAnchor, "middle"); @@ -54,7 +54,7 @@ it("text(dates, {frameAnchor}) has the expected defaults", () => { it("text(data, {title}) specifies an optional title channel", () => { const text = Plot.text(undefined, {title: "x"}); - const title = text.channels.find(c => c.name === "title"); + const {title} = text.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -72,7 +72,7 @@ it("text(data, {fill}) allows fill to be null", () => { it("text(data, {fill}) allows fill to be a variable color", () => { const text = Plot.text(undefined, {fill: "x"}); assert.strictEqual(text.fill, undefined); - const fill = text.channels.find(c => c.name === "fill"); + const {fill} = text.channels; assert.strictEqual(fill.value, "x"); assert.strictEqual(fill.scale, "color"); }); @@ -99,7 +99,7 @@ it("text(data, {fontSize}) allows fontSize to be a number, length, keyword, or p it("text(data, {fontSize}) allows fontSize to be a channel", () => { const text = Plot.text(undefined, {fontSize: "x"}); assert.strictEqual(text.fontSize, undefined); - assert.strictEqual(text.channels.find(c => c.name === "fontSize").value, "x"); + assert.strictEqual(text.channels.fontSize.value, "x"); }); it("text({length}) can take length-only data", () => { diff --git a/test/marks/tick-test.js b/test/marks/tick-test.js index 85f716f903..bc2c65bdf0 100644 --- a/test/marks/tick-test.js +++ b/test/marks/tick-test.js @@ -5,9 +5,9 @@ it("tickX() has the expected defaults", () => { const tick = Plot.tickX(); assert.strictEqual(tick.data, undefined); assert.strictEqual(tick.transform, undefined); - assert.deepStrictEqual(tick.channels.map(c => c.name), ["x"]); - assert.deepStrictEqual(tick.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3]]); - assert.deepStrictEqual(tick.channels.map(c => c.scale), ["x"]); + assert.deepStrictEqual(Object.keys(tick.channels), ["x"]); + assert.deepStrictEqual(Object.values(tick.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3]]); + assert.deepStrictEqual(Object.values(tick.channels).map(c => c.scale), ["x"]); assert.strictEqual(tick.fill, undefined); assert.strictEqual(tick.fillOpacity, undefined); assert.strictEqual(tick.stroke, "currentColor"); @@ -24,15 +24,15 @@ it("tickX() has the expected defaults", () => { it("tickX(data, {y}) uses a band scale", () => { const tick = Plot.tickX(undefined, {y: "x"}); - assert.deepStrictEqual(tick.channels.map(c => c.name), ["x", "y"]); - assert.deepStrictEqual(tick.channels.map(c => c.scale), ["x", "y"]); - assert.strictEqual(tick.channels.find(c => c.name === "y").type, "band"); - assert.strictEqual(tick.channels.find(c => c.name === "y").value, "x"); + assert.deepStrictEqual(Object.keys(tick.channels), ["x", "y"]); + assert.deepStrictEqual(Object.values(tick.channels).map(c => c.scale), ["x", "y"]); + assert.strictEqual(tick.channels.y.type, "band"); + assert.strictEqual(tick.channels.y.value, "x"); }); it("tickX(data, {title}) specifies an optional title channel", () => { const tick = Plot.tickX(undefined, {title: "x"}); - const title = tick.channels.find(c => c.name === "title"); + const {title} = tick.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -50,7 +50,7 @@ it("tickX(data, {stroke}) allows stroke to be null", () => { it("tickX(data, {stroke}) allows stroke to be a variable color", () => { const tick = Plot.tickX(undefined, {stroke: "x"}); assert.strictEqual(tick.stroke, undefined); - const stroke = tick.channels.find(c => c.name === "stroke"); + const {stroke} = tick.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); @@ -59,9 +59,9 @@ it("tickY() has the expected defaults", () => { const tick = Plot.tickY(); assert.strictEqual(tick.data, undefined); assert.strictEqual(tick.transform, undefined); - assert.deepStrictEqual(tick.channels.map(c => c.name), ["y"]); - assert.deepStrictEqual(tick.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3]]); - assert.deepStrictEqual(tick.channels.map(c => c.scale), ["y"]); + assert.deepStrictEqual(Object.keys(tick.channels), ["y"]); + assert.deepStrictEqual(Object.values(tick.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3]]); + assert.deepStrictEqual(Object.values(tick.channels).map(c => c.scale), ["y"]); assert.strictEqual(tick.fill, undefined); assert.strictEqual(tick.fillOpacity, undefined); assert.strictEqual(tick.stroke, "currentColor"); @@ -78,15 +78,15 @@ it("tickY() has the expected defaults", () => { it("tickY(data, {x}) uses a band scale", () => { const tick = Plot.tickY(undefined, {x: "y"}); - assert.deepStrictEqual(tick.channels.map(c => c.name), ["y", "x"]); - assert.deepStrictEqual(tick.channels.map(c => c.scale), ["y", "x"]); - assert.strictEqual(tick.channels.find(c => c.name === "x").type, "band"); - assert.strictEqual(tick.channels.find(c => c.name === "x").value, "y"); + assert.deepStrictEqual(Object.keys(tick.channels), ["y", "x"]); + assert.deepStrictEqual(Object.values(tick.channels).map(c => c.scale), ["y", "x"]); + assert.strictEqual(tick.channels.x.type, "band"); + assert.strictEqual(tick.channels.x.value, "y"); }); it("tickY(data, {title}) specifies an optional title channel", () => { const tick = Plot.tickY(undefined, {title: "x"}); - const title = tick.channels.find(c => c.name === "title"); + const {title} = tick.channels; assert.strictEqual(title.value, "x"); assert.strictEqual(title.scale, undefined); }); @@ -104,7 +104,7 @@ it("tickY(data, {stroke}) allows stroke to be null", () => { it("tickY(data, {stroke}) allows stroke to be a variable color", () => { const tick = Plot.tickY(undefined, {stroke: "x"}); assert.strictEqual(tick.stroke, undefined); - const stroke = tick.channels.find(c => c.name === "stroke"); + const {stroke} = tick.channels; assert.strictEqual(stroke.value, "x"); assert.strictEqual(stroke.scale, "color"); }); diff --git a/test/output/meander.svg b/test/output/meander.svg new file mode 100644 index 0000000000..c1b62c86c7 --- /dev/null +++ b/test/output/meander.svg @@ -0,0 +1,1089 @@ + + + + + 0.0 + + + 0.1 + + + 0.2 + + + 0.3 + + + 0.4 + + + 0.5 + + + 0.6 + + + 0.7 + + + 0.8 + + + 0.9 + + + 1.0 + + + + + 0.0 + + + 0.1 + + + 0.2 + + + 0.3 + + + 0.4 + + + 0.5 + + + 0.6 + + + 0.7 + + + 0.8 + + + 0.9 + + + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index b3f4d78380..3484ff59dc 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -114,6 +114,7 @@ export {default as linearRegressionPenguins} from "./linear-regression-penguins. export {default as likertSurvey} from "./likert-survey.js"; export {default as logDegenerate} from "./log-degenerate.js"; export {default as markovChain} from "./markov-chain.js"; +export {default as meander} from "./meander.js"; export {default as metroInequality} from "./metro-inequality.js"; export {default as metroInequalityChange} from "./metro-inequality-change.js"; export {default as metroUnemployment} from "./metro-unemployment.js"; diff --git a/test/plots/meander.js b/test/plots/meander.js new file mode 100644 index 0000000000..47a839b961 --- /dev/null +++ b/test/plots/meander.js @@ -0,0 +1,36 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function () { + const n = 1000; + const random = d3.randomLcg(42); + const data = {length: n}; + const X = Float64Array.from(data, random); + const Y = Float64Array.from(data, random); + const VX = Float64Array.from(data, () => (random() - 0.5 + Math.sign(random() - 0.5)) * 0.01); + const VY = Float64Array.from(data, () => (random() - 0.5 + Math.sign(random() - 0.5)) * 0.01); + const dot = Plot.dot(data, {x: X, y: Y}); + const plot = Plot.plot({ + inset: 1.5 + 1.5 + 1, + x: { + domain: [0, 1] + }, + y: { + domain: [0, 1] + }, + marks: [Plot.frame(), dot] + }); + requestAnimationFrame(function tick() { + if (!document.contains(plot)) return; + for (let i = 0; i < n; ++i) { + X[i] += VX[i]; + Y[i] += VY[i]; + if (X[i] <= 0 || X[i] >= 1) VX[i] *= -1; + if (Y[i] <= 0 || Y[i] >= 1) VY[i] *= -1; + } + plot + .replot({mark: dot, x: X, y: Y, animation: 250}) + .then(tick); + }); + return plot; +}