From 9924a6b10d655ac14c11ec84bc072c5d9fbd7e6e Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 14 Jul 2022 16:24:34 -0700 Subject: [PATCH 1/2] mark.channels is an object --- src/channel.js | 6 ++--- src/marks/dot.js | 5 ++-- src/plot.js | 8 +++--- test/marks/area-test.js | 34 ++++++++++++------------ test/marks/bar-test.js | 52 ++++++++++++++++++------------------- test/marks/cell-test.js | 32 +++++++++++------------ test/marks/dot-test.js | 14 +++++----- test/marks/frame-test.js | 2 +- test/marks/image-test.js | 14 +++++----- test/marks/line-test.js | 18 ++++++------- test/marks/link-test.js | 10 +++---- test/marks/rect-test.js | 12 ++++----- test/marks/rule-test.js | 56 ++++++++++++++++++++-------------------- test/marks/text-test.js | 20 +++++++------- test/marks/tick-test.js | 36 +++++++++++++------------- 15 files changed, 159 insertions(+), 160 deletions(-) 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..20cde59c51 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.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 diff --git a/src/plot.js b/src/plot.js index c34571fb50..50d38fcdf5 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"; @@ -266,7 +266,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 +278,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 +287,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/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"); }); From 3cef609df457a33c24c4e243a585c92c62585bba Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 14 Jul 2022 19:25:34 -0700 Subject: [PATCH 2/2] object all the channels --- src/channel.js | 19 +++++++------------ src/marks/area.js | 14 +++++++------- src/marks/arrow.js | 12 ++++++------ src/marks/bar.js | 20 ++++++++++---------- src/marks/cell.js | 8 ++++---- src/marks/delaunay.js | 30 +++++++++++++++--------------- src/marks/density.js | 12 ++++++------ src/marks/dot.js | 14 +++++++------- src/marks/image.js | 14 +++++++------- src/marks/line.js | 10 +++++----- src/marks/linearRegression.js | 10 +++++----- src/marks/link.js | 12 ++++++------ src/marks/rect.js | 12 ++++++------ src/marks/rule.js | 20 ++++++++++---------- src/marks/text.js | 14 +++++++------- src/marks/tick.js | 16 ++++++++-------- src/marks/vector.js | 12 ++++++------ src/options.js | 20 ++++++++++++++++++++ src/plot.js | 28 ++++++++++------------------ src/style.js | 22 +++++++++++----------- src/transforms/dodge.js | 4 ++-- test/marks/cell-test.js | 12 ++++++------ 22 files changed, 171 insertions(+), 164 deletions(-) diff --git a/src/channel.js b/src/channel.js index e5b8460d98..0241e7a56c 100644 --- a/src/channel.js +++ b/src/channel.js @@ -15,23 +15,18 @@ export function Channel(data, {scale, type, value, filter, hint}) { }; } -export function Channels(channelDescriptors, data) { - const channels = {}; - for (const name in channelDescriptors) { - channels[name] = Channel(data, channelDescriptors[name]); - } - return channels; +export function Channels(descriptors, data) { + return Object.fromEntries(Object.entries(descriptors).map(([name, channel]) => { + return [name, Channel(data, channel)]; + })); } // TODO Use Float64Array for scales with numeric ranges, e.g. position? export function valueObject(channels, scales) { - const values = {}; - for (const channelName in channels) { - const {scale: scaleName, value} = channels[channelName]; + return Object.fromEntries(Object.entries(channels).map(([name, {scale: scaleName, value}]) => { const scale = scales[scaleName]; - values[channelName] = scale === undefined ? value : map(value, scale); - } - return values; + return [name, scale === undefined ? value : map(value, scale)]; + })); } // Note: mutates channel.domain! This is set to a function so that it is lazily diff --git a/src/marks/area.js b/src/marks/area.js index 5f99fa2751..a4ebaf1647 100644 --- a/src/marks/area.js +++ b/src/marks/area.js @@ -21,13 +21,13 @@ export class Area extends Mark { const {x1, y1, x2, y2, z, curve, tension} = options; super( data, - [ - {name: "x1", value: x1, scale: "x"}, - {name: "y1", value: y1, scale: "y"}, - {name: "x2", value: x2, scale: "x", optional: true}, - {name: "y2", value: y2, scale: "y", optional: true}, - {name: "z", value: maybeZ(options), optional: true} - ], + { + x1: {value: x1, scale: "x"}, + y1: {value: y1, scale: "y"}, + x2: {value: x2, scale: "x", optional: true}, + y2: {value: y2, scale: "y", optional: true}, + z: {value: maybeZ(options), optional: true} + }, options, defaults ); diff --git a/src/marks/arrow.js b/src/marks/arrow.js index d6c3618543..5e0b82b433 100644 --- a/src/marks/arrow.js +++ b/src/marks/arrow.js @@ -30,12 +30,12 @@ export class Arrow extends Mark { } = options; super( data, - [ - {name: "x1", value: x1, scale: "x"}, - {name: "y1", value: y1, scale: "y"}, - {name: "x2", value: x2, scale: "x", optional: true}, - {name: "y2", value: y2, scale: "y", optional: true} - ], + { + x1: {value: x1, scale: "x"}, + y1: {value: y1, scale: "y"}, + x2: {value: x2, scale: "x", optional: true}, + y2: {value: y2, scale: "y", optional: true} + }, options, defaults ); diff --git a/src/marks/bar.js b/src/marks/bar.js index 3fe2e5d243..a1a23b9095 100644 --- a/src/marks/bar.js +++ b/src/marks/bar.js @@ -66,11 +66,11 @@ export class BarX extends AbstractBar { const {x1, x2, y} = options; super( data, - [ - {name: "x1", value: x1, scale: "x"}, - {name: "x2", value: x2, scale: "x"}, - {name: "y", value: y, scale: "y", type: "band", optional: true} - ], + { + x1: {value: x1, scale: "x"}, + x2: {value: x2, scale: "x"}, + y: {value: y, scale: "y", type: "band", optional: true} + }, options, defaults ); @@ -93,11 +93,11 @@ export class BarY extends AbstractBar { const {x, y1, y2} = options; super( data, - [ - {name: "y1", value: y1, scale: "y"}, - {name: "y2", value: y2, scale: "y"}, - {name: "x", value: x, scale: "x", type: "band", optional: true} - ], + { + y1: {value: y1, scale: "y"}, + y2: {value: y2, scale: "y"}, + x: {value: x, scale: "x", type: "band", optional: true} + }, options, defaults ); diff --git a/src/marks/cell.js b/src/marks/cell.js index 9f612782fd..250cdf0eb5 100644 --- a/src/marks/cell.js +++ b/src/marks/cell.js @@ -10,10 +10,10 @@ export class Cell extends AbstractBar { constructor(data, {x, y, ...options} = {}) { super( data, - [ - {name: "x", value: x, scale: "x", type: "band", optional: true}, - {name: "y", value: y, scale: "y", type: "band", optional: true} - ], + { + x: {value: x, scale: "x", type: "band", optional: true}, + y: {value: y, scale: "y", type: "band", optional: true} + }, options, defaults ); diff --git a/src/marks/delaunay.js b/src/marks/delaunay.js index 0cf1ad76a9..0510671443 100644 --- a/src/marks/delaunay.js +++ b/src/marks/delaunay.js @@ -47,11 +47,11 @@ class DelaunayLink extends Mark { const {x, y, z, curve, tension} = options; super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y", value: y, scale: "y", optional: true}, - {name: "z", value: z, optional: true} - ], + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + z: {value: z, optional: true} + }, options, delaunayLinkDefaults ); @@ -129,11 +129,11 @@ class AbstractDelaunayMark extends Mark { const {x, y} = options; super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y", value: y, scale: "y", optional: true}, - {name: "z", value: zof(options), optional: true} - ], + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + z: {value: zof(options), optional: true} + }, options, defaults ); @@ -188,11 +188,11 @@ class Voronoi extends Mark { const {x, y, z} = options; super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y", value: y, scale: "y", optional: true}, - {name: "z", value: z, optional: true} - ], + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + z: {value: z, optional: true} + }, options, voronoiDefaults ); diff --git a/src/marks/density.js b/src/marks/density.js index 16e0459bc2..1b02bf8180 100644 --- a/src/marks/density.js +++ b/src/marks/density.js @@ -22,12 +22,12 @@ export class Density extends Mark { const strokeDensity = isDensity(stroke) && (stroke = "currentColor", true); super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y", value: y, scale: "y", optional: true}, - {name: "z", value: maybeZ({z, fill, stroke}), optional: true}, - {name: "weight", value: weight, optional: true} - ], + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + z: {value: maybeZ({z, fill, stroke}), optional: true}, + weight: {value: weight, optional: true} + }, densityInitializer({...options, fill, stroke}, fillDensity, strokeDensity), defaults ); diff --git a/src/marks/dot.js b/src/marks/dot.js index 20cde59c51..e4140b5530 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.js @@ -23,13 +23,13 @@ export class Dot extends Mark { const [vr, cr] = maybeNumberChannel(r, vsymbol == null ? 3 : 4.5); super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y", value: y, scale: "y", optional: true}, - {name: "r", value: vr, scale: "r", filter: positive, optional: true}, - {name: "rotate", value: vrotate, optional: true}, - {name: "symbol", value: vsymbol, scale: "symbol", optional: true} - ], + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + r: {value: vr, scale: "r", filter: positive, optional: true}, + rotate: {value: vrotate, optional: true}, + symbol: {value: vsymbol, scale: "symbol", optional: true} + }, options.sort === undefined && options.reverse === undefined ? sort({channel: "r", order: "descending"}, options) : options, defaults ); diff --git a/src/marks/image.js b/src/marks/image.js index a99bcb1855..1f620898c9 100644 --- a/src/marks/image.js +++ b/src/marks/image.js @@ -42,13 +42,13 @@ export class Image extends Mark { const [vh, ch] = maybeNumberChannel(height, 16); super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y", value: y, scale: "y", optional: true}, - {name: "width", value: vw, filter: positive, optional: true}, - {name: "height", value: vh, filter: positive, optional: true}, - {name: "src", value: vs, optional: true} - ], + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + width: {value: vw, filter: positive, optional: true}, + height: {value: vh, filter: positive, optional: true}, + src: {value: vs, optional: true} + }, options, defaults ); diff --git a/src/marks/line.js b/src/marks/line.js index cfbe56ccff..aee309e170 100644 --- a/src/marks/line.js +++ b/src/marks/line.js @@ -22,11 +22,11 @@ export class Line extends Mark { const {x, y, z, curve, tension} = options; super( data, - [ - {name: "x", value: x, scale: "x"}, - {name: "y", value: y, scale: "y"}, - {name: "z", value: maybeZ(options), optional: true} - ], + { + x: {value: x, scale: "x"}, + y: {value: y, scale: "y"}, + z: {value: maybeZ(options), optional: true} + }, options, defaults ); diff --git a/src/marks/linearRegression.js b/src/marks/linearRegression.js index 3d0606c7e0..0c4650c3d0 100644 --- a/src/marks/linearRegression.js +++ b/src/marks/linearRegression.js @@ -22,11 +22,11 @@ class LinearRegression extends Mark { const {x, y, z, ci = 0.95, precision = 4} = options; super( data, - [ - {name: "x", value: x, scale: "x"}, - {name: "y", value: y, scale: "y"}, - {name: "z", value: maybeZ(options), optional: true} - ], + { + x: {value: x, scale: "x"}, + y: {value: y, scale: "y"}, + z: {value: maybeZ(options), optional: true} + }, options, defaults ); diff --git a/src/marks/link.js b/src/marks/link.js index 4e353e5ffe..b404391a73 100644 --- a/src/marks/link.js +++ b/src/marks/link.js @@ -17,12 +17,12 @@ export class Link extends Mark { const {x1, y1, x2, y2, curve, tension} = options; super( data, - [ - {name: "x1", value: x1, scale: "x"}, - {name: "y1", value: y1, scale: "y"}, - {name: "x2", value: x2, scale: "x", optional: true}, - {name: "y2", value: y2, scale: "y", optional: true} - ], + { + x1: {value: x1, scale: "x"}, + y1: {value: y1, scale: "y"}, + x2: {value: x2, scale: "x", optional: true}, + y2: {value: y2, scale: "y", optional: true} + }, options, defaults ); diff --git a/src/marks/rect.js b/src/marks/rect.js index 4b49118867..8377d1341d 100644 --- a/src/marks/rect.js +++ b/src/marks/rect.js @@ -28,12 +28,12 @@ export class Rect extends Mark { } = options; super( data, - [ - {name: "x1", value: x1, scale: "x", optional: true}, - {name: "y1", value: y1, scale: "y", optional: true}, - {name: "x2", value: x2, scale: "x", optional: true}, - {name: "y2", value: y2, scale: "y", optional: true} - ], + { + x1: {value: x1, scale: "x", optional: true}, + y1: {value: y1, scale: "y", optional: true}, + x2: {value: x2, scale: "x", optional: true}, + y2: {value: y2, scale: "y", optional: true} + }, options, defaults ); diff --git a/src/marks/rule.js b/src/marks/rule.js index b14ccb35ae..ff9ea8e43e 100644 --- a/src/marks/rule.js +++ b/src/marks/rule.js @@ -23,11 +23,11 @@ export class RuleX extends Mark { } = options; super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y1", value: y1, scale: "y", optional: true}, - {name: "y2", value: y2, scale: "y", optional: true} - ], + { + x: {value: x, scale: "x", optional: true}, + y1: {value: y1, scale: "y", optional: true}, + y2: {value: y2, scale: "y", optional: true} + }, options, defaults ); @@ -68,11 +68,11 @@ export class RuleY extends Mark { } = options; super( data, - [ - {name: "y", value: y, scale: "y", optional: true}, - {name: "x1", value: x1, scale: "x", optional: true}, - {name: "x2", value: x2, scale: "x", optional: true} - ], + { + y: {value: y, scale: "y", optional: true}, + x1: {value: x1, scale: "x", optional: true}, + x2: {value: x2, scale: "x", optional: true} + }, options, defaults ); diff --git a/src/marks/text.js b/src/marks/text.js index 55740ec010..20bd21b2a3 100644 --- a/src/marks/text.js +++ b/src/marks/text.js @@ -37,13 +37,13 @@ export class Text extends Mark { const [vfontSize, cfontSize] = maybeFontSizeChannel(fontSize); super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y", value: y, scale: "y", optional: true}, - {name: "fontSize", value: vfontSize, optional: true}, - {name: "rotate", value: numberChannel(vrotate), optional: true}, - {name: "text", value: text, filter: nonempty} - ], + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + fontSize: {value: vfontSize, optional: true}, + rotate: {value: numberChannel(vrotate), optional: true}, + text: {value: text, filter: nonempty} + }, options, defaults ); diff --git a/src/marks/tick.js b/src/marks/tick.js index 600ec3b9b6..8717718386 100644 --- a/src/marks/tick.js +++ b/src/marks/tick.js @@ -42,10 +42,10 @@ export class TickX extends AbstractTick { } = options; super( data, - [ - {name: "x", value: x, scale: "x"}, - {name: "y", value: y, scale: "y", type: "band", optional: true} - ], + { + x: {value: x, scale: "x"}, + y: {value: y, scale: "y", type: "band", optional: true} + }, options ); this.insetTop = number(insetTop); @@ -81,10 +81,10 @@ export class TickY extends AbstractTick { } = options; super( data, - [ - {name: "y", value: y, scale: "y"}, - {name: "x", value: x, scale: "x", type: "band", optional: true} - ], + { + y: {value: y, scale: "y"}, + x: {value: x, scale: "x", type: "band", optional: true} + }, options ); this.insetRight = number(insetRight); diff --git a/src/marks/vector.js b/src/marks/vector.js index 0d67db6753..86e28ce363 100644 --- a/src/marks/vector.js +++ b/src/marks/vector.js @@ -19,12 +19,12 @@ export class Vector extends Mark { const [vr, cr] = maybeNumberChannel(rotate, 0); super( data, - [ - {name: "x", value: x, scale: "x", optional: true}, - {name: "y", value: y, scale: "y", optional: true}, - {name: "length", value: vl, scale: "length", optional: true}, - {name: "rotate", value: vr, optional: true} - ], + { + x: {value: x, scale: "x", optional: true}, + y: {value: y, scale: "y", optional: true}, + length: {value: vl, scale: "length", optional: true}, + rotate: {value: vr, optional: true} + }, options, defaults ); diff --git a/src/options.js b/src/options.js index 5bf307d46e..8f25ecaaeb 100644 --- a/src/options.js +++ b/src/options.js @@ -362,3 +362,23 @@ export function inherit(options = {}, ...rest) { } return o; } + +// Given an iterable of named things (objects with a name property), returns a +// corresponding object with properties associated with the given name. +export function Named(things) { + console.warn("named iterables are deprecated; please use an object instead"); + const names = new Set(); + return Object.fromEntries(Array.from(things, thing => { + const {name} = thing; + if (name == null) throw new Error("missing name"); + const key = `${name}`; + if (key === "__proto__") throw new Error(`illegal name: ${key}`); + if (names.has(key)) throw new Error(`duplicate name: ${key}`); + names.add(key); + return [name, thing]; + })); +} + +export function maybeNamed(things) { + return isIterable(things) ? Named(things) : things; +} diff --git a/src/plot.js b/src/plot.js index 50d38fcdf5..99b4777fed 100644 --- a/src/plot.js +++ b/src/plot.js @@ -5,7 +5,7 @@ import {Context, create} from "./context.js"; import {defined} from "./defined.js"; import {Dimensions} from "./dimensions.js"; import {Legends, exposeLegends} from "./legends.js"; -import {arrayify, isDomainSort, isScaleOptions, keyword, map, range, second, where, yes} from "./options.js"; +import {arrayify, isDomainSort, isScaleOptions, keyword, map, maybeNamed, range, second, where, yes} from "./options.js"; import {Scales, ScaleFunctions, autoScaleRange, exposeScales} from "./scales.js"; import {position, registry as scaleRegistry} from "./scales/index.js"; import {applyInlineStyles, maybeClassName, maybeClip, styles} from "./style.js"; @@ -256,29 +256,21 @@ export function plot(options = {}) { } export class Mark { - constructor(data, channels = [], options = {}, defaults) { + constructor(data, channels = {}, options = {}, defaults) { const {facet = "auto", sort, dx, dy, clip, channels: extraChannels} = options; - const names = new Set(); this.data = data; this.sort = isDomainSort(sort) ? sort : null; this.initializer = initializer(options).initializer; this.transform = this.initializer ? options.transform : basic(options).transform; 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 = Object.fromEntries(channels.filter(channel => { - const {name, value, optional} = channel; - if (value == null) { - if (optional) return false; - throw new Error(`missing channel value: ${name}`); - } - if (name == null) throw new Error("missing channel name"); - const key = `${name}`; - if (key === "__proto__") throw new Error(`illegal channel name: ${key}`); - if (names.has(key)) throw new Error(`duplicate channel: ${key}`); - names.add(key); - return true; - }).map(channel => [channel.name, channel])); + channels = maybeNamed(channels); + if (extraChannels !== undefined) channels = {...maybeNamed(extraChannels), ...channels}; + if (defaults !== undefined) channels = {...styles(this, options, defaults), ...channels}; + this.channels = Object.fromEntries(Object.entries(channels).filter(([name, {value, optional}]) => { + if (value != null) return true; + if (optional) return false; + throw new Error(`missing channel value: ${name}`); + })); this.dx = +dx || 0; this.dy = +dy || 0; this.clip = maybeClip(clip); diff --git a/src/style.js b/src/style.js index c8fe8b5570..1bc98a9369 100644 --- a/src/style.js +++ b/src/style.js @@ -125,17 +125,17 @@ export function styles( mark.pointerEvents = impliedString(pointerEvents, "auto"); mark.shapeRendering = impliedString(shapeRendering, "auto"); - return [ - {name: "title", value: title, optional: true}, - {name: "href", value: href, optional: true}, - {name: "ariaLabel", value: variaLabel, optional: true}, - {name: "fill", value: vfill, scale: "color", optional: true}, - {name: "fillOpacity", value: vfillOpacity, scale: "opacity", optional: true}, - {name: "stroke", value: vstroke, scale: "color", optional: true}, - {name: "strokeOpacity", value: vstrokeOpacity, scale: "opacity", optional: true}, - {name: "strokeWidth", value: vstrokeWidth, optional: true}, - {name: "opacity", value: vopacity, scale: "opacity", optional: true} - ]; + return { + title: {value: title, optional: true}, + href: {value: href, optional: true}, + ariaLabel: {value: variaLabel, optional: true}, + fill: {value: vfill, scale: "color", optional: true}, + fillOpacity: {value: vfillOpacity, scale: "opacity", optional: true}, + stroke: {value: vstroke, scale: "color", optional: true}, + strokeOpacity: {value: vstrokeOpacity, scale: "opacity", optional: true}, + strokeWidth: {value: vstrokeWidth, optional: true}, + opacity: {value: vopacity, scale: "opacity", optional: true} + }; } // Applies the specified titles via selection.call. diff --git a/src/transforms/dodge.js b/src/transforms/dodge.js index c267450f13..fc68567075 100644 --- a/src/transforms/dodge.js +++ b/src/transforms/dodge.js @@ -1,6 +1,6 @@ import IntervalTree from "interval-tree-1d"; import {finite, positive} from "../defined.js"; -import {identity, number, valueof} from "../options.js"; +import {identity, maybeNamed, number, valueof} from "../options.js"; import {coerceNumbers} from "../scales.js"; import {initializer} from "./basic.js"; @@ -48,7 +48,7 @@ function dodge(y, x, anchor, padding, options) { const {r} = options; if (r != null && typeof r !== "number") { const {channels, sort, reverse} = options; - options = {...options, channels: [...channels ?? [], {name: "r", value: r, scale: "r"}]}; + options = {...options, channels: {r: {value: r, scale: "r"}, ...maybeNamed(channels)}}; if (sort === undefined && reverse === undefined) options.sort = {channel: "r", order: "descending"}; } return initializer(options, function(data, facets, {[x]: X, r: R}, scales, dimensions) { diff --git a/test/marks/cell-test.js b/test/marks/cell-test.js index 978d6d9d6e..97971df62c 100644 --- a/test/marks/cell-test.js +++ b/test/marks/cell-test.js @@ -75,9 +75,9 @@ 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(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.deepStrictEqual(Object.keys(cell.channels), ["fill", "x"]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3], [0, 1, 2]]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => c.scale), ["color", "x"]); assert.strictEqual(cell.channels.x.type, "band"); }); @@ -85,8 +85,8 @@ 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(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.deepStrictEqual(Object.keys(cell.channels), ["fill", "y"]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => Plot.valueof([1, 2, 3], c.value)), [[1, 2, 3], [0, 1, 2]]); + assert.deepStrictEqual(Object.values(cell.channels).map(c => c.scale), ["color", "y"]); assert.strictEqual(cell.channels.y.type, "band"); });