From a91564882f428296b6ddc9198917f6c7ac755bcd Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 2 Feb 2023 12:12:11 -0800 Subject: [PATCH 01/12] per-channel scale override; "auto" scale --- src/channel.js | 50 +- src/mark.js | 21 +- src/marks/dot.js | 2 +- src/options.js | 19 +- src/plot.js | 25 +- src/scales.js | 26 +- src/style.js | 4 +- test/marks/area-test.js | 4 +- test/marks/bar-test.js | 8 +- test/marks/cell-test.js | 8 +- test/marks/dot-test.js | 4 +- test/marks/line-test.js | 4 +- test/marks/link-test.js | 2 +- test/marks/rect-test.js | 4 +- test/marks/rule-test.js | 4 +- test/marks/text-test.js | 2 +- test/marks/tick-test.js | 4 +- test/output/usCongressAgeColorExplicit.svg | 1776 +++++++++--------- test/plots/us-congress-age-color-explicit.js | 1 + 19 files changed, 984 insertions(+), 984 deletions(-) diff --git a/src/channel.js b/src/channel.js index bce56f0ca5..3609e4a8d1 100644 --- a/src/channel.js +++ b/src/channel.js @@ -1,22 +1,45 @@ import {ascending, descending, rollup, sort} from "d3"; -import {first, isIterable, labelof, map, maybeValue, range, valueof} from "./options.js"; +import {first, isColor, isEvery, isIterable, labelof, map, maybeValue, range, valueof} from "./options.js"; import {registry} from "./scales/index.js"; +import {isSymbol, maybeSymbol} from "./symbols.js"; import {maybeReduce} from "./transforms/group.js"; // TODO Type coercion? -export function Channel(data, {scale, type, value, filter, hint}) { +export function Channel(data, {scale, type, value, filter, hint}, name) { + let V = valueof(data, value); + + // If the channel uses the "auto" scale, infer the scale from the channel name + // and the provided values. For color and symbol channels, no scale is applied + // if the values are literal; however for symbols, we must promote symbol + // names (e.g., "plus") to symbol implementations (symbolPlus). + if (scale === "auto") { + scale = inferChannelScale(name); + if (V) { + if (scale === "color") { + if (isEvery(V, isColor)) { + scale = undefined; + } + } else if (scale === "symbol") { + if (isEvery(V, isSymbol)) { + V = map(V, maybeSymbol); + scale = undefined; + } + } + } + } + return { scale, type, - value: valueof(data, value), + value: V, label: labelof(value), filter, hint }; } -export function Channels(descriptors, data) { - return Object.fromEntries(Object.entries(descriptors).map(([name, channel]) => [name, Channel(data, channel)])); +export function Channels(channels, data) { + return Object.fromEntries(Object.entries(channels).map(([name, channel]) => [name, Channel(data, channel, name)])); } // TODO Use Float64Array for scales with numeric ranges, e.g. position? @@ -24,14 +47,25 @@ export function valueObject(channels, scales) { return Object.fromEntries( Object.entries(channels).map(([name, {scale: scaleName, value}]) => { let scale; - if (scaleName !== undefined) { - scale = scales[scaleName]; - } + if (scaleName != null) scale = scales[scaleName]; return [name, scale === undefined ? value : map(value, scale)]; }) ); } +// Returns the default scale for the channel with the given name. +export function inferChannelScale(name) { + switch (name) { + case "fill": + case "stroke": + return "color"; + case "fillOpacity": + case "strokeOpacity": + return "opacity"; + } + return registry.has(name) ? name : null; +} + // Note: mutates channel.domain! This is set to a function so that it is lazily // computed; i.e., if the scale’s domain is set explicitly, that takes priority // over the sort option, and we don’t need to do additional work. diff --git a/src/mark.js b/src/mark.js index 1e44ef5179..ec54b70800 100644 --- a/src/mark.js +++ b/src/mark.js @@ -1,7 +1,7 @@ import {Channels, channelDomain, valueObject} from "./channel.js"; import {defined} from "./defined.js"; import {maybeFacetAnchor} from "./facet.js"; -import {arrayify, isDomainSort, range} from "./options.js"; +import {arrayify, isDomainSort, isOptions, range} from "./options.js"; import {keyword, maybeNamed} from "./options.js"; import {maybeProject} from "./projection.js"; import {maybeClip, styles} from "./style.js"; @@ -41,11 +41,20 @@ export class Mark { 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}`); - }) + Object.entries(channels) + .map(([name, channel]) => { + const {value} = channel; + if (isOptions(value)) { + channel = {...channel, value: value.value}; + if (value.scale !== undefined) channel.scale = value.scale; + } + return [name, channel]; + }) + .filter(([name, {value, optional}]) => { + if (value != null) return true; + if (optional) return false; + throw new Error(`missing channel value: ${name}`); + }) ); this.dx = +dx; this.dy = +dy; diff --git a/src/marks/dot.js b/src/marks/dot.js index 93f176b94b..634e4c6d1c 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.js @@ -41,7 +41,7 @@ export class Dot extends Mark { 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} + symbol: {value: vsymbol, scale: "auto", optional: true} }, withDefaultSort(options), defaults diff --git a/src/options.js b/src/options.js index 47cdcb4f72..7ee83d095b 100644 --- a/src/options.js +++ b/src/options.js @@ -336,23 +336,18 @@ export function isNumeric(values) { } } -export function isFirst(values, is) { - for (const value of values) { - if (value == null) continue; - return is(value); - } -} - -// Whereas isFirst only tests the first defined value and returns undefined for -// an empty array, this tests all defined values and only returns true if all of -// them are valid colors. It also returns true for an empty array, and thus -// should generally be used in conjunction with isFirst. +// Returns true if every non-null value in the specified iterable of values +// passes the specified predicate, and there is at least one non-null value; +// returns false if at least one non-null value does not pass the specified +// predicate; otherwise returns undefined (as if all values are null). export function isEvery(values, is) { + let every; for (const value of values) { if (value == null) continue; if (!is(value)) return false; + every = true; } - return true; + return every; } // Mostly relies on d3-color, with a few extra color keywords. Currently this diff --git a/src/plot.js b/src/plot.js index 9a462c893b..e9bf3f2973 100644 --- a/src/plot.js +++ b/src/plot.js @@ -1,5 +1,5 @@ import {select} from "d3"; -import {Channel} from "./channel.js"; +import {Channel, inferChannelScale} from "./channel.js"; import {Context, create} from "./context.js"; import {Dimensions} from "./dimensions.js"; import {Facets, facetExclude, facetGroups, facetOrder, facetTranslate, facetFilter} from "./facet.js"; @@ -156,7 +156,7 @@ export function plot(options = {}) { state.facets = update.facets; } if (update.channels !== undefined) { - inferChannelScale(update.channels, mark); + inferChannelScales(update.channels); Object.assign(state.channels, update.channels); for (const channel of Object.values(update.channels)) { const {scale} = channel; @@ -370,27 +370,10 @@ function applyScaleTransform(channel, options) { // An initializer may generate channels without knowing how the downstream mark // will use them. Marks are typically responsible associated scales with // channels, but here we assume common behavior across marks. -function inferChannelScale(channels) { +function inferChannelScales(channels) { for (const name in channels) { const channel = channels[name]; - let {scale} = channel; - if (scale === true) { - switch (name) { - case "fill": - case "stroke": - scale = "color"; - break; - case "fillOpacity": - case "strokeOpacity": - case "opacity": - scale = "opacity"; - break; - default: - scale = scaleRegistry.has(name) ? name : null; - break; - } - channel.scale = scale; - } + if (channel.scale === true) channel.scale = inferChannelScale(name); } } diff --git a/src/scales.js b/src/scales.js index 3d690eeed2..c7ff0ee8ed 100644 --- a/src/scales.js +++ b/src/scales.js @@ -1,9 +1,6 @@ import {parse as isoParse} from "isoformat"; import { - isColor, - isEvery, isOrdinal, - isFirst, isTemporal, isTemporalString, isNumericString, @@ -34,7 +31,7 @@ import { import {isDivergingScheme} from "./scales/schemes.js"; import {ScaleTime, ScaleUtc} from "./scales/temporal.js"; import {ScaleOrdinal, ScalePoint, ScaleBand, ordinalImplicit} from "./scales/ordinal.js"; -import {isSymbol, maybeSymbol} from "./symbols.js"; +import {maybeSymbol} from "./symbols.js"; import {warn} from "./warnings.js"; export function Scales( @@ -406,20 +403,8 @@ function inferScaleType(key, channels, {type, domain, range, scheme, pivot, proj // If there’s no data (and no type) associated with this scale, don’t create a scale. if (domain === undefined && !channels.some(({value}) => value !== undefined)) return; - const kind = registry.get(key); - - // For color scales, if no range or scheme is specified and all associated - // defined values (from the domain if present, and otherwise from channels) - // are valid colors, then default to the identity scale. This allows, for - // example, a fill channel to return literal colors; without this, the colors - // would be remapped to a categorical scheme! - if (kind === color && range === undefined && scheme === undefined && isAll(domain, channels, isColor)) - return "identity"; - - // Similarly for symbols… - if (kind === symbol && range === undefined && isAll(domain, channels, isSymbol)) return "identity"; - // Some scales have default types. + const kind = registry.get(key); if (kind === radius) return "sqrt"; if (kind === opacity || kind === length) return "linear"; if (kind === symbol) return "ordinal"; @@ -461,13 +446,6 @@ function asOrdinalType(kind) { } } -function isAll(domain, channels, is) { - return domain !== undefined - ? isFirst(domain, is) && isEvery(domain, is) - : channels.some(({value}) => value !== undefined && isFirst(value, is)) && - channels.every(({value}) => value === undefined || isEvery(value, is)); -} - export function isTemporalScale({type}) { return type === "time" || type === "utc"; } diff --git a/src/style.js b/src/style.js index 083c5754ab..09185365c8 100644 --- a/src/style.js +++ b/src/style.js @@ -143,9 +143,9 @@ export function styles( title: {value: title, optional: true}, href: {value: href, optional: true}, ariaLabel: {value: variaLabel, optional: true}, - fill: {value: vfill, scale: "color", optional: true}, + fill: {value: vfill, scale: "auto", optional: true}, fillOpacity: {value: vfillOpacity, scale: "opacity", optional: true}, - stroke: {value: vstroke, scale: "color", optional: true}, + stroke: {value: vstroke, scale: "auto", optional: true}, strokeOpacity: {value: vstrokeOpacity, scale: "opacity", optional: true}, strokeWidth: {value: vstrokeWidth, optional: true}, opacity: {value: vopacity, scale: "opacity", optional: true} diff --git a/test/marks/area-test.js b/test/marks/area-test.js index 1a10aa42bd..f65ba8dca9 100644 --- a/test/marks/area-test.js +++ b/test/marks/area-test.js @@ -73,7 +73,7 @@ it("area(data, {fill}) allows fill to be a variable color", () => { assert.strictEqual(area.fill, undefined); const {fill} = area.channels; assert.strictEqual(fill.value, "x"); - assert.strictEqual(fill.scale, "color"); + assert.strictEqual(fill.scale, "auto"); }); it("area(data, {fill}) implies a default z channel if fill is variable", () => { @@ -98,7 +98,7 @@ it("area(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(area.stroke, undefined); const {stroke} = area.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("area(data, {stroke}) implies a default z channel if stroke is variable", () => { diff --git a/test/marks/bar-test.js b/test/marks/bar-test.js index 031228532f..9dbeb3ea05 100644 --- a/test/marks/bar-test.js +++ b/test/marks/bar-test.js @@ -62,7 +62,7 @@ it("barX(data, {fill}) allows fill to be a variable color", () => { assert.strictEqual(bar.fill, undefined); const {fill} = bar.channels; assert.strictEqual(fill.value, "x"); - assert.strictEqual(fill.scale, "color"); + assert.strictEqual(fill.scale, "auto"); }); it("barX(data, {stroke}) allows stroke to be a constant color", () => { @@ -80,7 +80,7 @@ it("barX(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(bar.stroke, undefined); const {stroke} = bar.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("barX(data, {x, y}) defaults x1 to zero and x2 to x", () => { @@ -162,7 +162,7 @@ it("barY(data, {fill}) allows fill to be a variable color", () => { assert.strictEqual(bar.fill, undefined); const {fill} = bar.channels; assert.strictEqual(fill.value, "x"); - assert.strictEqual(fill.scale, "color"); + assert.strictEqual(fill.scale, "auto"); }); it("barY(data, {stroke}) allows stroke to be a constant color", () => { @@ -180,7 +180,7 @@ it("barY(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(bar.stroke, undefined); const {stroke} = bar.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("barY(data, {x, y}) defaults y1 to zero and y2 to y", () => { diff --git a/test/marks/cell-test.js b/test/marks/cell-test.js index 6517f5eef6..bcf744ddfe 100644 --- a/test/marks/cell-test.js +++ b/test/marks/cell-test.js @@ -67,7 +67,7 @@ it("cell(data, {fill}) allows fill to be a variable color", () => { assert.strictEqual(cell.fill, undefined); const {fill} = cell.channels; assert.strictEqual(fill.value, "x"); - assert.strictEqual(fill.scale, "color"); + assert.strictEqual(fill.scale, "auto"); }); it("cell(data, {stroke}) allows stroke to be a constant color", () => { @@ -85,7 +85,7 @@ it("cell(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(cell.stroke, undefined); const {stroke} = cell.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("cellX() defaults x to identity and y to null", () => { @@ -102,7 +102,7 @@ it("cellX() defaults x to identity and y to null", () => { ); assert.deepStrictEqual( Object.values(cell.channels).map((c) => c.scale), - ["color", "x"] + ["auto", "x"] ); assert.strictEqual(cell.channels.x.type, "band"); }); @@ -121,7 +121,7 @@ it("cellY() defaults y to identity and x to null", () => { ); assert.deepStrictEqual( Object.values(cell.channels).map((c) => c.scale), - ["color", "y"] + ["auto", "y"] ); assert.strictEqual(cell.channels.y.type, "band"); }); diff --git a/test/marks/dot-test.js b/test/marks/dot-test.js index bb874d33ea..635e365a7c 100644 --- a/test/marks/dot-test.js +++ b/test/marks/dot-test.js @@ -79,7 +79,7 @@ it("dot(data, {fill}) allows fill to be a variable color", () => { assert.strictEqual(dot.fill, undefined); const {fill} = dot.channels; assert.strictEqual(fill.value, "x"); - assert.strictEqual(fill.scale, "color"); + assert.strictEqual(fill.scale, "auto"); }); it("dot(data, {fill}) defaults stroke to undefined if fill is not none", () => { @@ -103,7 +103,7 @@ it("dot(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(dot.stroke, undefined); const {stroke} = dot.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("dot(data, {stroke}) defaults strokeWidth to 1.5 if stroke is defined", () => { diff --git a/test/marks/line-test.js b/test/marks/line-test.js index 494337420a..d60c58fc20 100644 --- a/test/marks/line-test.js +++ b/test/marks/line-test.js @@ -70,7 +70,7 @@ it("line(data, {fill}) allows fill to be a variable color", () => { assert.strictEqual(line.fill, undefined); const {fill} = line.channels; assert.strictEqual(fill.value, "x"); - assert.strictEqual(fill.scale, "color"); + assert.strictEqual(fill.scale, "auto"); }); it("line(data, {fill}) implies a default z channel if fill is variable", () => { @@ -100,7 +100,7 @@ it("line(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(line.stroke, undefined); const {stroke} = line.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("line(data, {stroke}) implies a default z channel if stroke is variable", () => { diff --git a/test/marks/link-test.js b/test/marks/link-test.js index 26824412fd..4dc9a8b546 100644 --- a/test/marks/link-test.js +++ b/test/marks/link-test.js @@ -50,5 +50,5 @@ it("link(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(link.stroke, undefined); const {stroke} = link.channels; assert.strictEqual(stroke.value, "4"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); diff --git a/test/marks/rect-test.js b/test/marks/rect-test.js index ba92cab17f..c97d251fb5 100644 --- a/test/marks/rect-test.js +++ b/test/marks/rect-test.js @@ -54,7 +54,7 @@ it("rect(data, {fill}) allows fill to be a variable color", () => { assert.strictEqual(rect.fill, undefined); const {fill} = rect.channels; assert.strictEqual(fill.value, "4"); - assert.strictEqual(fill.scale, "color"); + assert.strictEqual(fill.scale, "auto"); }); it("rect(data, {stroke}) allows stroke to be a constant color", () => { @@ -72,5 +72,5 @@ it("rect(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(rect.stroke, undefined); const {stroke} = rect.channels; assert.strictEqual(stroke.value, "4"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); diff --git a/test/marks/rule-test.js b/test/marks/rule-test.js index e33c76453a..8973398725 100644 --- a/test/marks/rule-test.js +++ b/test/marks/rule-test.js @@ -50,7 +50,7 @@ it("ruleX(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(rule.stroke, undefined); const {stroke} = rule.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("ruleX(data, {x, y}) specifies y1 = zero, y2 = y", () => { @@ -145,7 +145,7 @@ it("ruleY(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(rule.stroke, undefined); const {stroke} = rule.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("ruleY(data, {x, y}) specifies x1 = zero, x2 = x", () => { diff --git a/test/marks/text-test.js b/test/marks/text-test.js index 742528a935..853ec1d454 100644 --- a/test/marks/text-test.js +++ b/test/marks/text-test.js @@ -98,7 +98,7 @@ it("text(data, {fill}) allows fill to be a variable color", () => { assert.strictEqual(text.fill, undefined); const {fill} = text.channels; assert.strictEqual(fill.value, "x"); - assert.strictEqual(fill.scale, "color"); + assert.strictEqual(fill.scale, "auto"); }); it("text(data, {stroke}) has a default strokeLinejoin of round", () => { diff --git a/test/marks/tick-test.js b/test/marks/tick-test.js index 156d78ce6b..1c3571a9dd 100644 --- a/test/marks/tick-test.js +++ b/test/marks/tick-test.js @@ -61,7 +61,7 @@ it("tickX(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(tick.stroke, undefined); const {stroke} = tick.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); it("tickY() has the expected defaults", () => { @@ -124,5 +124,5 @@ it("tickY(data, {stroke}) allows stroke to be a variable color", () => { assert.strictEqual(tick.stroke, undefined); const {stroke} = tick.channels; assert.strictEqual(stroke.value, "x"); - assert.strictEqual(stroke.scale, "color"); + assert.strictEqual(stroke.scale, "auto"); }); diff --git a/test/output/usCongressAgeColorExplicit.svg b/test/output/usCongressAgeColorExplicit.svg index 67653cf19a..cb55b4bda9 100644 --- a/test/output/usCongressAgeColorExplicit.svg +++ b/test/output/usCongressAgeColorExplicit.svg @@ -66,894 +66,894 @@ Age → - - Alexandria Ocasio-Cortez - Abby Finkenauer - Katie Hill - Josh Harder - Lauren Underwood - Max Rose - Elise M. Stefanik - Mike Gallagher - Conor Lamb - Joe Neguse - Xochitl Torres Small - Anthony Gonzalez - William R. Timmons IV - Dan Crenshaw - Patrick Murphy - Trey Hollingsworth - Haley M. Stevens - Guy Reschenthaler - Colin Z. Allred - Matt Gaetz - Andy Kim - Joe Cunningham - Lance Gooden - Jared F. Golden - Aaron Schock - Tulsi Gabbard - Michael F. Q. San Nicolas - Ilhan Omar - Bryan Steil - Carlos Curbelo - Ruben J. Kihuen - Justin Amash - Eric Swalwell - Joseph P. Kennedy III - Jason Smith - Lee M. Zeldin - Brian J. Mast - Sharice Davids - Chris Pappas - Scott Taylor - Ruben Gallego - Pete Aguilar - Jim Banks - Jason Crow - Abigail Davis Spanberger - Josh Hawley - Ron DeSantis - Jaime Herrera Beutler - Adam Kinzinger - Seth Moulton - Stephanie N. Murphy - Darren Soto - Mike Levin - W. Gregory Steube - Anthony Brindisi - David G. Valadao - Tom Cotton - Markwayne Mullin - Brendan F. Boyle - Will Hurd - Antonio Delgado - Benjamin Quayle - Trey Radel - Marlin A. Stutzman - Kevin Yoder - Ryan A. Costello - Duncan Hunter - Martha Roby - Kyrsten Sinema - Ro Khanna - Nanette Diaz Barragán - Jenniffer González-Colón - Steve Watkins - Elissa Slotkin - Rashida Tlaib - Kelly Armstrong - Kendra S. Horn - Dusty Johnson - Mike Garcia - Jim Bridenstine - Jared Polis - Mia B. Love - Patrick T. McHenry - Grace Meng - Josh Gottheimer - Michael Cloud - Lizzie Fletcher - Elaine G. Luria - Vance M. McAllister - André Carson - Cory Gardner - Joaquin Castro - Derek Kilmer - Jimmy Gomez - Katie Porter - Michael Waltz - Ayanna Pressley - Ben McAdams - Dan Boren - Jon Runyan - Stephen Lee Fincher - Christopher Murphy - Devin Nunes - Cedric L. Richmond - Tim Ryan - Andy Barr - Raja Krishnamoorthi - Brian K. Fitzpatrick - Steven Horsford - Jahana Hayes - Russ Fulcher - Lori Trahan - David W. Jolly - Beto O’Rourke - Thomas A. Garrett, Jr. - Ben Ray Luján - Todd Young - Brian Schatz - Raul Ruiz - Garret Graves - David Rouzer - Ben Sasse - James Comer - Mike Johnson - Jodey C. Arrington - Angie Craig - Mikie Sherrill - Van Taylor - Chip Roy - Ben Cline - Heath Shuler - Kristi L. Noem - Sean P. Duffy - Martin Heinrich - Mike Lee - Tom Reed - Marco Rubio - Thomas Massie - Richard Hudson - Marc A. Veasey - Alexander X. Mooney - Ted Budd - Gilbert Ray Cisneros, Jr. - Debbie Mucarsel-Powell - Sean Casten - Jeffrey M. Landry - Michael G. Grimm - Frank C. Guinta - Todd Rokita - Thomas J. Rooney - Paul D. Ryan - Tom Graves - Steven M. Palazzo - Adrian Smith - Rob Woodall - Rodney Davis - Hakeem S. Jeffries - Ted Cruz - Joni Ernst - Warren Davidson - Greg Stanton - Michael Guest - Denver Riggleman - Kelly Loeffler - William M. Cowan - Robert Hurt - Robert J. Dold - Luke Messer - Bill Huizenga - Cathy McMorris Rodgers - Austin Scott - Linda T. Sánchez - Cory A. Booker - Ted Lieu - Mark Walker - Jimmy Panetta - Dean Phillips - Veronica Escobar - Jason Altmire - Tim Griffin - Daniel B. Maffei - Kelly Ayotte - Tim Huelskamp - David Young - James Lankford - Tammy Duckworth - George Holding - Darin LaHood - Jennifer Wexton - Kim Schrier - Connie Mack - Mark Takai - Mick Mulvaney - Jason Chaffetz - Jeff Denham - Raúl R. Labrador - Mike Bishop - Bruce Westerman - Vicente Gonzalez - Chrissy Houlahan - Randy Hultgren - Stephen Knight - Kirsten E. Gillibrand - Kathy Castor - Eric A. "Rick" Crawford - Theodore E. Deutch - Jeff Duncan - James A. Himes - Daniel Lipinski - Debbie Wasserman Schultz - Doug Collins - Sean Patrick Maloney - Stacey E. Plaskett - Trent Kelly - A. Drew Ferguson IV - David Kustoff - Liz Cheney - Ross Spano - Pete Stauber - Susie Lee - Martha McSally - Chris Jacobs - Jesse L. Jackson Jr. - David Rivera - John Sullivan - Jeff Chiesa - Steve Southerland II - Erik Paulsen - John Ratcliffe - Robert B. Aderholt - Rick Larsen - Kevin McCarthy - Steve Scalise - Tim Scott - Terri A. Sewell - Adam Smith - Steve Stivers - Ami Bera - Norma J. Torres - Kathleen M. Rice - Pramila Jayapal - Cynthia Axne - Tom Malinowski - John W. Rose - Fred Keller - Robert T. Schilling - Renee L. Ellmers - Christopher P. Gibson - Trey Gowdy - Timothy J. Walz - Dave Brat - Michael F. Bennet - Yvette D. Clarke - Scott DesJarlais - Brett Guthrie - Jim Jordan - James R. Langevin - Jared Huffman - Mark Pocan - Dan Sullivan - Kamala D. Harris - Catherine Cortez Masto - Salud O. Carbajal - Lloyd Smucker - Daniel Meuser - Tim Burchett - Mark E. Green - Dan Bishop - Betty Sutton - Eric Cantor - Mark L. Pryor - Mike Rogers - Joe Garcia - Michael G. Fitzpatrick - Gwen Graham - Mike Pompeo - Keith Ellison - Lynn Jenkins - John K. Delaney - Steve Russell - Christopher A. Coons - Gus M. Bilirakis - Sam Graves - Ron Kind - Rand Paul - Tony Cárdenas - Jackie Walorski - Filemon Vela - Katherine M. Clark - Barry Loudermilk - Don Bacon - TJ Cox - Gregory F. Murphy - Mark S. Critz - Todd Russell Platts - Laura Richardson - Mark Begich - Lee Terry - Patrick J. Tiberi - Joseph Crowley - Jeff Flake - Keith J. Rothfus - Mimi Walters - Karen C. Handel - Tammy Baldwin - Larry Bucshon - Charles J. "Chuck" Fleischmann - Michael T. McCaul - Pete Olson - John P. Sarbanes - David Schweikert - Suzan K. DelBene - Ann Wagner - Steve Daines - Scott Perry - John Katko - Lisa Blunt Rochester - Jamie Raskin - Thomas R. Suozzi - Troy Balderson - Jim Hagedorn - Mary Bono Mack - Mike Ross - Joe Walsh - Allen B. West - Pete P. Gallego - David Vitter - Joseph J. Heck - Ryan K. Zinke - Blake Farenthold - Peter J. Roskam - Bill Shuster - Claudia Tenney - David N. Cicilline - Mario Diaz-Balart - John Thune - Patrick J. Toomey - Juan Vargas - Cheri Bustos - Kevin Cramer - Matt Cartwright - John R. Moolenaar - Tom Emmer - Bradley Scott Schneider - Clay Higgins - Anthony G. Brown - Paul Mitchell - A. Donald McEachin - Greg Gianforte - Kevin Hern - Harley Rouda - Jim Matheson - John E. Walsh - E. Scott Rigell - Loretta Sanchez - Charles W. Dent - Evan H. Jenkins - Dean Heller - Mark Sanford - David A. Trott - Thomas MacArthur - Robert P. Casey, Jr. - Amy Klobuchar - Jeff Fortenberry - Vicky Hartzler - Frank D. Lucas - Adam B. Schiff - Michael R. Turner - Doug LaMalfa - Mark Takano - Susan W. Brooks - Chris Stewart - Jody B. Hice - Mike Bost - Thom Tillis - Roger W. Marshall - John R. Curtis - Lucy McBath - Andy Levin - Debra A. Haaland - Scott P. Brown - Rick Berg - Ben Chandler - Chip Cravaack - Nan A. S. Hayworth - Mike Pence - Jo Bonner - Mark Kirk - Scott Garrett - Jeff Miller - Pedro R. Pierluisi - Curt Clawson - Michelle Lujan Grisham - Dennis A. Ross - Elizabeth H. Esty - Barbara Comstock - Brenda Jones - Mark Meadows - Brian Higgins - James P. McGovern - Glenn Thompson - Chris Van Hollen - Robert J. Wittman - Ken Buck - Cindy Hyde-Smith - Mary Gay Scanlon - Madeleine Dean - Enid Greene Waldholtz - Steve Austria - Russ Carnahan - Kathleen C. Hochul - Alan Nunnelee - Donna F. Edwards - Steve Israel - Matt Salmon - Alan Grayson - Xavier Becerra - James B. Renacci - Maria Cantwell - Paul A. Gosar - H. Morgan Griffith - Gary C. Peters - Mike Quigley - Mike Rogers - John Shimkus - Mac Thornberry - Mark E. Amodei - Donald M. Payne, Jr. - Scott H. Peters - Daniel T. Kildee - Brad R. Wenstrup - Tim Kaine - Donald Norcross - Margaret Wood Hassan - Andy Biggs - J. Luis Correa - Tina Smith - Debbie Lesko - Hansen Clarke - Tim Holden - Robert E. Andrews - Bruce L. Braley - Cresent Hardy - Trent Franks - Jeb Hensarling - Bill Cassidy - Diana DeGette - Andy Harris - John Hoeven - Lisa Murkowski - Greg Walden - Steve Womack - David P. Joyce - Tom Rice - Earl L. "Buddy" Carter - Val Butler Demings - Jacky Rosen - Joseph D. Morelle - Susan Wild - John Joyce - Thomas P. Tiffany - Sandy Adams - Michele Bachmann - Mike McIntyre - Steve Stockman - Chaka Fattah - Charles W. Boustany Jr. - John C. Carney Jr. - Reid J. Ribble - Lou Barletta - John Abney Culberson - Gregg Harper - Daniel M. Donovan, Jr. - Jon Tester - Jeff Merkley - Wm. Lacy Clay - Robert E. Latta - Tom McClintock - Scott R. Tipton - Ann M. Kuster - Robin L. Kelly - J. French Hill - Charlie Crist - Ron Estes - Jesús G. "Chuy" García - Greg Pence - Denny Rehberg - Mary L. Landrieu - John Barrow - John Campbell - Jim Gerlach - Jack Kingston - Michael H. Michaud - Patrick Meehan - Mike Coffman - Joe Donnelly - Pete Sessions - Heidi Heitkamp - Rod Blum - Jason Lewis - Sheldon Whitehouse - Lindsey Graham - Kevin Brady - Richard Burr - Henry Cuellar - Ron Johnson - Billy Long - Stephen F. Lynch - Chellie Pingree - Rob Portman - Gregorio Kilili Camacho Sablan - Bill Foster - Ted S. Yoho - Bradley Byrne - Dan Newhouse - Glenn Grothman - David J. Trone - Steven C. LaTourette - Candice S. Miller - Cynthia M. Lummis - Tom Price - Robert Menendez - Mark R. Warner - Mo Brooks - Jim Cooper - Bill Flores - Bob Gibbs - Bill Johnson - Henry C. "Hank" Johnson, Jr. - Doug Lamborn - Betty McCollum - Jerry Moran - Brad Sherman - Suzanne Bonamici - Gary J. Palmer - Ralph Lee Abraham - Brenda L. Lawrence - Mike Rounds - Dwight Evans - Adriano Espaillat - Doug Jones - Mike Braun - Brad Miller - Kay R. Hagan - Dave Camp - Luther Strange - Claire McCaskill - Luis V. Gutiérrez - Darrell E. Issa - Bruce Poliquin - Karen Bass - Ken Calvert - Shelley Moore Capito - Steve Chabot - Judy Chu - Joe Courtney - Michael F. Doyle - Louie Gohmert - Gregory W. Meeks - Ed Perlmutter - Christopher H. Smith - Fred Upton - Nydia M. Velázquez - Randy K. Weber, Sr. - Debbie Dingell - Neal P. Dunn - Francis Rooney - Ralph Norman - Jefferson Van Drew - Ron Wright - Charles F. Bass - David Dreier - Steven R. Rothman - Janice Hahn - Dan Benishek - J. Randy Forbes - Tim Murphy - Bob Corker - Michael E. Capuano - Bob Goodlatte - Leonard Lance - Ileana Ros-Lehtinen - Carol Shea-Porter - John J. Faso - Tom Marino - Sherrod Brown - John Barrasso - Susan M. Collins - John Cornyn - Marsha Blackburn - Jim Costa - Marcia L. Fudge - William R. Keating - David Loebsack - Blaine Luetkemeyer - Julia Brownley - Denny Heck - Mark DeSaulnier - John H. Rutherford - Ed Case - Rick Scott - Shelley Berkley - Brian P. Bilbray - Ann Marie Buerkle - Larry Kissell - Jean Schmidt - Jim DeMint - John F. Tierney - Kerry L. Bentivolio - John Fleming - Richard L. Hanna - Richard B. Nugent - Al Franken - Diane Black - Edward R. Royce - Colleen Hanabusa - Elijah E. Cummings - Roger F. Wicker - Rob Bishop - Vern Buchanan - Mike Crapo - Kenny Marchant - Jerry McNerney - Gwen Moore - Frank Pallone, Jr. - Kurt Schrader - Albio Sires - Mike Thompson - Tim Walberg - Deb Fischer - Rick W. Allen - John Kennedy - Jo Ann Emerson - Mark Udall - Timothy H. Bishop - Mike Johanns - Lynn A. Westmoreland - David G. Reichert - Chris Collins - Debbie Stabenow - Roy Blunt - John Boozman - Michael C. Burgess - Gerald E. Connolly - Sheila Jackson Lee - Patty Murray - Charles E. Schumer - Michael K. Simpson - Jackie Speier - Dina Titus - Joyce Beatty - Donald S. Beyer, Jr. - Ann Kirkpatrick - Sylvia R. Garcia - Carol D. Miller - Francisco "Quico" Canseco - Jerry F. Costello - William L. Owens - Nick J. Rahall II - William L. Enyart - John A. Boehner - Randy Neugebauer - Brad Ashford - Joe Barton - Jack Reed - Steve Cohen - Tom Cole - Steve King - Richard E. Neal - Paul Tonko - Peter J. Visclosky - Daniel Webster - Ron Wyden - Elizabeth Warren - Roger Williams - David Perdue - Kent Conrad - David Alan Curson - Tom Coburn - Rush Holt - Tom Latham - Gary G. Miller - Allyson Y. Schwartz - Ted Poe - Robert Pittenger - Tom Udall - Earl Blumenauer - K. Michael Conaway - Raúl M. Grijalva - Mike Kelly - John B. Larson - Bennie G. Thompson - Lois Frankel - Brian Babin - Al Lawson, Jr. - Kweisi Mfume - Olympia J. Snowe - W. Todd Akin - Joe Baca - Spencer Bachus - John Kline - John J. Duncan, Jr. - Gene Green - Stevan Pearce - Dana Rohrabacher - Lamar Smith - Thomas R. Carper - Jeanne Shaheen - Joe Manchin, III - Sanford D. Bishop, Jr. - G. K. Butterfield - Peter A. DeFazio - Eliot L. Engel - Al Green - Mazie K. Hirono - Zoe Lofgren - David B. McKinley - Jerrold Nadler - Bill Posey - Robert C. "Bobby" Scott - Peter Welch - Joe Wilson - John A. Yarmuth - Aumua Amata Coleman Radewagen - Jack Bergman - Mitt Romney - Jim Webb - Timothy V. Johnson - Dennis J. Kucinich - Daniel E. Lungren - Rodney Alexander - Tim Johnson - Paul C. Broun - Corrine Brown - Jeff Sessions - Rodney P. Frelinghuysen - Frank A. LoBiondo - Niki Tsongas - Richard Blumenthal - Lloyd Doggett - Marcy Kaptur - Barbara Lee - Carolyn B. Maloney - Edward J. Markey - C. A. Dutch Ruppersberger - Bobby L. Rush - Alma S. Adams - Tom O’Halleran - Charles A. Gonzalez - Wally Herger - Melvin L. Watt - Donna M. Christensen - George Miller - James P. Moran - Ron Barber - Robert A. Brady - John Garamendi - David P. Roe - David Scott - Bonnie Watson Coleman - James R. Baird - Elton Gallegly - Donald A. Manzullo - Silvestre Reyes - Carolyn McCarthy - Ander Crenshaw - Johnny Isakson - Richard J. Durbin - Michael B. Enzi - Emanuel Cleaver - Susan A. Davis - Peter T. King - Doris O. Matsui - Collin C. Peterson - Janice D. Schakowsky - Angus S. King, Jr. - Jeff Bingaman - Kay Bailey Hutchison - John F. Kerry - Saxby Chambliss - Eni F. H. Faleomavaega - Ed Pastor - Ed Whitfield - Daniel Coats - John L. Mica - Richard M. Nolan - Walter B. Jones - Benjamin L. Cardin - James E. Risch - Rosa L. DeLauro - Virginia Foxx - Kay Granger - F. James Sensenbrenner, Jr. - José E. Serrano - Paul Cook - Bob Filner - Joseph I. Lieberman - Gary L. Ackerman - Phil Gingrey - Jon Kyl - Bill Nelson - Mitch McConnell - Anna G. Eshoo - Frederica S. Wilson - Ben Nelson - Howard L. Berman - Sue Wilkins Myrick - Cliff Stearns - Robert L. Turner - Max Baucus - Doc Hastings - Gloria Negrete McLeod - Sam Farr - Michael M. Honda - Bernard Sanders - John R. Carter - Danny K. Davis - Lucille Roybal-Allard - Alan S. Lowenthal - Donna E. Shalala - Norman D. Dicks - Barney Frank - Thomas E. Petri - Barbara Boxer - Rubén Hinojosa - John Lewis - Lamar Alexander - James E. Clyburn - Patrick J. Leahy - Nancy Pelosi - David E. Price - Tom Harkin - Henry A. Waxman - Frank R. Wolf - Harry Reid - Joseph R. Pitts - Steny H. Hoyer - Dan Burton - Maurice D. Hinchey - Howard P. "Buck" McKeon - Lois Capps - Maxine Waters - Judy Biggert - Lynn C. Woolsey - John D. Rockefeller, IV - Thad Cochran - Nita M. Lowey - Eleanor Holmes Norton - Bill Pascrell, Jr. - Harold Rogers - John W. Olver - Barbara A. Mikulski - Jim McDermott - John McCain - Pat Roberts - Alcee L. Hastings - Grace F. Napolitano - Herb Kohl - Ron Paul - Eddie Bernice Johnson - Leonard L. Boswell - Jerry Lewis - Edolphus Towns - Carl Levin - Orrin G. Hatch - James M. Inhofe - Richard C. Shelby - Madeleine Z. Bordallo - Dianne Feinstein - Chuck Grassley - Don Young - Richard G. Lugar - Fortney Pete Stark - Howard Coble - Sander M. Levin - C. W. Bill Young - Charles B. Rangel - Sam Johnson - Dale E. Kildee - John Conyers, Jr. - Louise McIntosh Slaughter - Roscoe G. Bartlett - John D. Dingell - Daniel K. Inouye - Daniel K. Akaka - Frank R. Lautenberg - Ralph M. Hall + + Alexandria Ocasio-Cortez + Abby Finkenauer + Katie Hill + Josh Harder + Lauren Underwood + Max Rose + Elise M. Stefanik + Mike Gallagher + Conor Lamb + Joe Neguse + Xochitl Torres Small + Anthony Gonzalez + William R. Timmons IV + Dan Crenshaw + Patrick Murphy + Trey Hollingsworth + Haley M. Stevens + Guy Reschenthaler + Colin Z. Allred + Matt Gaetz + Andy Kim + Joe Cunningham + Lance Gooden + Jared F. Golden + Aaron Schock + Tulsi Gabbard + Michael F. Q. San Nicolas + Ilhan Omar + Bryan Steil + Carlos Curbelo + Ruben J. Kihuen + Justin Amash + Eric Swalwell + Joseph P. Kennedy III + Jason Smith + Lee M. Zeldin + Brian J. Mast + Sharice Davids + Chris Pappas + Scott Taylor + Ruben Gallego + Pete Aguilar + Jim Banks + Jason Crow + Abigail Davis Spanberger + Josh Hawley + Ron DeSantis + Jaime Herrera Beutler + Adam Kinzinger + Seth Moulton + Stephanie N. Murphy + Darren Soto + Mike Levin + W. Gregory Steube + Anthony Brindisi + David G. Valadao + Tom Cotton + Markwayne Mullin + Brendan F. Boyle + Will Hurd + Antonio Delgado + Benjamin Quayle + Trey Radel + Marlin A. Stutzman + Kevin Yoder + Ryan A. Costello + Duncan Hunter + Martha Roby + Kyrsten Sinema + Ro Khanna + Nanette Diaz Barragán + Jenniffer González-Colón + Steve Watkins + Elissa Slotkin + Rashida Tlaib + Kelly Armstrong + Kendra S. Horn + Dusty Johnson + Mike Garcia + Jim Bridenstine + Jared Polis + Mia B. Love + Patrick T. McHenry + Grace Meng + Josh Gottheimer + Michael Cloud + Lizzie Fletcher + Elaine G. Luria + Vance M. McAllister + André Carson + Cory Gardner + Joaquin Castro + Derek Kilmer + Jimmy Gomez + Katie Porter + Michael Waltz + Ayanna Pressley + Ben McAdams + Dan Boren + Jon Runyan + Stephen Lee Fincher + Christopher Murphy + Devin Nunes + Cedric L. Richmond + Tim Ryan + Andy Barr + Raja Krishnamoorthi + Brian K. Fitzpatrick + Steven Horsford + Jahana Hayes + Russ Fulcher + Lori Trahan + David W. Jolly + Beto O’Rourke + Thomas A. Garrett, Jr. + Ben Ray Luján + Todd Young + Brian Schatz + Raul Ruiz + Garret Graves + David Rouzer + Ben Sasse + James Comer + Mike Johnson + Jodey C. Arrington + Angie Craig + Mikie Sherrill + Van Taylor + Chip Roy + Ben Cline + Heath Shuler + Kristi L. Noem + Sean P. Duffy + Martin Heinrich + Mike Lee + Tom Reed + Marco Rubio + Thomas Massie + Richard Hudson + Marc A. Veasey + Alexander X. Mooney + Ted Budd + Gilbert Ray Cisneros, Jr. + Debbie Mucarsel-Powell + Sean Casten + Jeffrey M. Landry + Michael G. Grimm + Frank C. Guinta + Todd Rokita + Thomas J. Rooney + Paul D. Ryan + Tom Graves + Steven M. Palazzo + Adrian Smith + Rob Woodall + Rodney Davis + Hakeem S. Jeffries + Ted Cruz + Joni Ernst + Warren Davidson + Greg Stanton + Michael Guest + Denver Riggleman + Kelly Loeffler + William M. Cowan + Robert Hurt + Robert J. Dold + Luke Messer + Bill Huizenga + Cathy McMorris Rodgers + Austin Scott + Linda T. Sánchez + Cory A. Booker + Ted Lieu + Mark Walker + Jimmy Panetta + Dean Phillips + Veronica Escobar + Jason Altmire + Tim Griffin + Daniel B. Maffei + Kelly Ayotte + Tim Huelskamp + David Young + James Lankford + Tammy Duckworth + George Holding + Darin LaHood + Jennifer Wexton + Kim Schrier + Connie Mack + Mark Takai + Mick Mulvaney + Jason Chaffetz + Jeff Denham + Raúl R. Labrador + Mike Bishop + Bruce Westerman + Vicente Gonzalez + Chrissy Houlahan + Randy Hultgren + Stephen Knight + Kirsten E. Gillibrand + Kathy Castor + Eric A. "Rick" Crawford + Theodore E. Deutch + Jeff Duncan + James A. Himes + Daniel Lipinski + Debbie Wasserman Schultz + Doug Collins + Sean Patrick Maloney + Stacey E. Plaskett + Trent Kelly + A. Drew Ferguson IV + David Kustoff + Liz Cheney + Ross Spano + Pete Stauber + Susie Lee + Martha McSally + Chris Jacobs + Jesse L. Jackson Jr. + David Rivera + John Sullivan + Jeff Chiesa + Steve Southerland II + Erik Paulsen + John Ratcliffe + Robert B. Aderholt + Rick Larsen + Kevin McCarthy + Steve Scalise + Tim Scott + Terri A. Sewell + Adam Smith + Steve Stivers + Ami Bera + Norma J. Torres + Kathleen M. Rice + Pramila Jayapal + Cynthia Axne + Tom Malinowski + John W. Rose + Fred Keller + Robert T. Schilling + Renee L. Ellmers + Christopher P. Gibson + Trey Gowdy + Timothy J. Walz + Dave Brat + Michael F. Bennet + Yvette D. Clarke + Scott DesJarlais + Brett Guthrie + Jim Jordan + James R. Langevin + Jared Huffman + Mark Pocan + Dan Sullivan + Kamala D. Harris + Catherine Cortez Masto + Salud O. Carbajal + Lloyd Smucker + Daniel Meuser + Tim Burchett + Mark E. Green + Dan Bishop + Betty Sutton + Eric Cantor + Mark L. Pryor + Mike Rogers + Joe Garcia + Michael G. Fitzpatrick + Gwen Graham + Mike Pompeo + Keith Ellison + Lynn Jenkins + John K. Delaney + Steve Russell + Christopher A. Coons + Gus M. Bilirakis + Sam Graves + Ron Kind + Rand Paul + Tony Cárdenas + Jackie Walorski + Filemon Vela + Katherine M. Clark + Barry Loudermilk + Don Bacon + TJ Cox + Gregory F. Murphy + Mark S. Critz + Todd Russell Platts + Laura Richardson + Mark Begich + Lee Terry + Patrick J. Tiberi + Joseph Crowley + Jeff Flake + Keith J. Rothfus + Mimi Walters + Karen C. Handel + Tammy Baldwin + Larry Bucshon + Charles J. "Chuck" Fleischmann + Michael T. McCaul + Pete Olson + John P. Sarbanes + David Schweikert + Suzan K. DelBene + Ann Wagner + Steve Daines + Scott Perry + John Katko + Lisa Blunt Rochester + Jamie Raskin + Thomas R. Suozzi + Troy Balderson + Jim Hagedorn + Mary Bono Mack + Mike Ross + Joe Walsh + Allen B. West + Pete P. Gallego + David Vitter + Joseph J. Heck + Ryan K. Zinke + Blake Farenthold + Peter J. Roskam + Bill Shuster + Claudia Tenney + David N. Cicilline + Mario Diaz-Balart + John Thune + Patrick J. Toomey + Juan Vargas + Cheri Bustos + Kevin Cramer + Matt Cartwright + John R. Moolenaar + Tom Emmer + Bradley Scott Schneider + Clay Higgins + Anthony G. Brown + Paul Mitchell + A. Donald McEachin + Greg Gianforte + Kevin Hern + Harley Rouda + Jim Matheson + John E. Walsh + E. Scott Rigell + Loretta Sanchez + Charles W. Dent + Evan H. Jenkins + Dean Heller + Mark Sanford + David A. Trott + Thomas MacArthur + Robert P. Casey, Jr. + Amy Klobuchar + Jeff Fortenberry + Vicky Hartzler + Frank D. Lucas + Adam B. Schiff + Michael R. Turner + Doug LaMalfa + Mark Takano + Susan W. Brooks + Chris Stewart + Jody B. Hice + Mike Bost + Thom Tillis + Roger W. Marshall + John R. Curtis + Lucy McBath + Andy Levin + Debra A. Haaland + Scott P. Brown + Rick Berg + Ben Chandler + Chip Cravaack + Nan A. S. Hayworth + Mike Pence + Jo Bonner + Mark Kirk + Scott Garrett + Jeff Miller + Pedro R. Pierluisi + Curt Clawson + Michelle Lujan Grisham + Dennis A. Ross + Elizabeth H. Esty + Barbara Comstock + Brenda Jones + Mark Meadows + Brian Higgins + James P. McGovern + Glenn Thompson + Chris Van Hollen + Robert J. Wittman + Ken Buck + Cindy Hyde-Smith + Mary Gay Scanlon + Madeleine Dean + Enid Greene Waldholtz + Steve Austria + Russ Carnahan + Kathleen C. Hochul + Alan Nunnelee + Donna F. Edwards + Steve Israel + Matt Salmon + Alan Grayson + Xavier Becerra + James B. Renacci + Maria Cantwell + Paul A. Gosar + H. Morgan Griffith + Gary C. Peters + Mike Quigley + Mike Rogers + John Shimkus + Mac Thornberry + Mark E. Amodei + Donald M. Payne, Jr. + Scott H. Peters + Daniel T. Kildee + Brad R. Wenstrup + Tim Kaine + Donald Norcross + Margaret Wood Hassan + Andy Biggs + J. Luis Correa + Tina Smith + Debbie Lesko + Hansen Clarke + Tim Holden + Robert E. Andrews + Bruce L. Braley + Cresent Hardy + Trent Franks + Jeb Hensarling + Bill Cassidy + Diana DeGette + Andy Harris + John Hoeven + Lisa Murkowski + Greg Walden + Steve Womack + David P. Joyce + Tom Rice + Earl L. "Buddy" Carter + Val Butler Demings + Jacky Rosen + Joseph D. Morelle + Susan Wild + John Joyce + Thomas P. Tiffany + Sandy Adams + Michele Bachmann + Mike McIntyre + Steve Stockman + Chaka Fattah + Charles W. Boustany Jr. + John C. Carney Jr. + Reid J. Ribble + Lou Barletta + John Abney Culberson + Gregg Harper + Daniel M. Donovan, Jr. + Jon Tester + Jeff Merkley + Wm. Lacy Clay + Robert E. Latta + Tom McClintock + Scott R. Tipton + Ann M. Kuster + Robin L. Kelly + J. French Hill + Charlie Crist + Ron Estes + Jesús G. "Chuy" García + Greg Pence + Denny Rehberg + Mary L. Landrieu + John Barrow + John Campbell + Jim Gerlach + Jack Kingston + Michael H. Michaud + Patrick Meehan + Mike Coffman + Joe Donnelly + Pete Sessions + Heidi Heitkamp + Rod Blum + Jason Lewis + Sheldon Whitehouse + Lindsey Graham + Kevin Brady + Richard Burr + Henry Cuellar + Ron Johnson + Billy Long + Stephen F. Lynch + Chellie Pingree + Rob Portman + Gregorio Kilili Camacho Sablan + Bill Foster + Ted S. Yoho + Bradley Byrne + Dan Newhouse + Glenn Grothman + David J. Trone + Steven C. LaTourette + Candice S. Miller + Cynthia M. Lummis + Tom Price + Robert Menendez + Mark R. Warner + Mo Brooks + Jim Cooper + Bill Flores + Bob Gibbs + Bill Johnson + Henry C. "Hank" Johnson, Jr. + Doug Lamborn + Betty McCollum + Jerry Moran + Brad Sherman + Suzanne Bonamici + Gary J. Palmer + Ralph Lee Abraham + Brenda L. Lawrence + Mike Rounds + Dwight Evans + Adriano Espaillat + Doug Jones + Mike Braun + Brad Miller + Kay R. Hagan + Dave Camp + Luther Strange + Claire McCaskill + Luis V. Gutiérrez + Darrell E. Issa + Bruce Poliquin + Karen Bass + Ken Calvert + Shelley Moore Capito + Steve Chabot + Judy Chu + Joe Courtney + Michael F. Doyle + Louie Gohmert + Gregory W. Meeks + Ed Perlmutter + Christopher H. Smith + Fred Upton + Nydia M. Velázquez + Randy K. Weber, Sr. + Debbie Dingell + Neal P. Dunn + Francis Rooney + Ralph Norman + Jefferson Van Drew + Ron Wright + Charles F. Bass + David Dreier + Steven R. Rothman + Janice Hahn + Dan Benishek + J. Randy Forbes + Tim Murphy + Bob Corker + Michael E. Capuano + Bob Goodlatte + Leonard Lance + Ileana Ros-Lehtinen + Carol Shea-Porter + John J. Faso + Tom Marino + Sherrod Brown + John Barrasso + Susan M. Collins + John Cornyn + Marsha Blackburn + Jim Costa + Marcia L. Fudge + William R. Keating + David Loebsack + Blaine Luetkemeyer + Julia Brownley + Denny Heck + Mark DeSaulnier + John H. Rutherford + Ed Case + Rick Scott + Shelley Berkley + Brian P. Bilbray + Ann Marie Buerkle + Larry Kissell + Jean Schmidt + Jim DeMint + John F. Tierney + Kerry L. Bentivolio + John Fleming + Richard L. Hanna + Richard B. Nugent + Al Franken + Diane Black + Edward R. Royce + Colleen Hanabusa + Elijah E. Cummings + Roger F. Wicker + Rob Bishop + Vern Buchanan + Mike Crapo + Kenny Marchant + Jerry McNerney + Gwen Moore + Frank Pallone, Jr. + Kurt Schrader + Albio Sires + Mike Thompson + Tim Walberg + Deb Fischer + Rick W. Allen + John Kennedy + Jo Ann Emerson + Mark Udall + Timothy H. Bishop + Mike Johanns + Lynn A. Westmoreland + David G. Reichert + Chris Collins + Debbie Stabenow + Roy Blunt + John Boozman + Michael C. Burgess + Gerald E. Connolly + Sheila Jackson Lee + Patty Murray + Charles E. Schumer + Michael K. Simpson + Jackie Speier + Dina Titus + Joyce Beatty + Donald S. Beyer, Jr. + Ann Kirkpatrick + Sylvia R. Garcia + Carol D. Miller + Francisco "Quico" Canseco + Jerry F. Costello + William L. Owens + Nick J. Rahall II + William L. Enyart + John A. Boehner + Randy Neugebauer + Brad Ashford + Joe Barton + Jack Reed + Steve Cohen + Tom Cole + Steve King + Richard E. Neal + Paul Tonko + Peter J. Visclosky + Daniel Webster + Ron Wyden + Elizabeth Warren + Roger Williams + David Perdue + Kent Conrad + David Alan Curson + Tom Coburn + Rush Holt + Tom Latham + Gary G. Miller + Allyson Y. Schwartz + Ted Poe + Robert Pittenger + Tom Udall + Earl Blumenauer + K. Michael Conaway + Raúl M. Grijalva + Mike Kelly + John B. Larson + Bennie G. Thompson + Lois Frankel + Brian Babin + Al Lawson, Jr. + Kweisi Mfume + Olympia J. Snowe + W. Todd Akin + Joe Baca + Spencer Bachus + John Kline + John J. Duncan, Jr. + Gene Green + Stevan Pearce + Dana Rohrabacher + Lamar Smith + Thomas R. Carper + Jeanne Shaheen + Joe Manchin, III + Sanford D. Bishop, Jr. + G. K. Butterfield + Peter A. DeFazio + Eliot L. Engel + Al Green + Mazie K. Hirono + Zoe Lofgren + David B. McKinley + Jerrold Nadler + Bill Posey + Robert C. "Bobby" Scott + Peter Welch + Joe Wilson + John A. Yarmuth + Aumua Amata Coleman Radewagen + Jack Bergman + Mitt Romney + Jim Webb + Timothy V. Johnson + Dennis J. Kucinich + Daniel E. Lungren + Rodney Alexander + Tim Johnson + Paul C. Broun + Corrine Brown + Jeff Sessions + Rodney P. Frelinghuysen + Frank A. LoBiondo + Niki Tsongas + Richard Blumenthal + Lloyd Doggett + Marcy Kaptur + Barbara Lee + Carolyn B. Maloney + Edward J. Markey + C. A. Dutch Ruppersberger + Bobby L. Rush + Alma S. Adams + Tom O’Halleran + Charles A. Gonzalez + Wally Herger + Melvin L. Watt + Donna M. Christensen + George Miller + James P. Moran + Ron Barber + Robert A. Brady + John Garamendi + David P. Roe + David Scott + Bonnie Watson Coleman + James R. Baird + Elton Gallegly + Donald A. Manzullo + Silvestre Reyes + Carolyn McCarthy + Ander Crenshaw + Johnny Isakson + Richard J. Durbin + Michael B. Enzi + Emanuel Cleaver + Susan A. Davis + Peter T. King + Doris O. Matsui + Collin C. Peterson + Janice D. Schakowsky + Angus S. King, Jr. + Jeff Bingaman + Kay Bailey Hutchison + John F. Kerry + Saxby Chambliss + Eni F. H. Faleomavaega + Ed Pastor + Ed Whitfield + Daniel Coats + John L. Mica + Richard M. Nolan + Walter B. Jones + Benjamin L. Cardin + James E. Risch + Rosa L. DeLauro + Virginia Foxx + Kay Granger + F. James Sensenbrenner, Jr. + José E. Serrano + Paul Cook + Bob Filner + Joseph I. Lieberman + Gary L. Ackerman + Phil Gingrey + Jon Kyl + Bill Nelson + Mitch McConnell + Anna G. Eshoo + Frederica S. Wilson + Ben Nelson + Howard L. Berman + Sue Wilkins Myrick + Cliff Stearns + Robert L. Turner + Max Baucus + Doc Hastings + Gloria Negrete McLeod + Sam Farr + Michael M. Honda + Bernard Sanders + John R. Carter + Danny K. Davis + Lucille Roybal-Allard + Alan S. Lowenthal + Donna E. Shalala + Norman D. Dicks + Barney Frank + Thomas E. Petri + Barbara Boxer + Rubén Hinojosa + John Lewis + Lamar Alexander + James E. Clyburn + Patrick J. Leahy + Nancy Pelosi + David E. Price + Tom Harkin + Henry A. Waxman + Frank R. Wolf + Harry Reid + Joseph R. Pitts + Steny H. Hoyer + Dan Burton + Maurice D. Hinchey + Howard P. "Buck" McKeon + Lois Capps + Maxine Waters + Judy Biggert + Lynn C. Woolsey + John D. Rockefeller, IV + Thad Cochran + Nita M. Lowey + Eleanor Holmes Norton + Bill Pascrell, Jr. + Harold Rogers + John W. Olver + Barbara A. Mikulski + Jim McDermott + John McCain + Pat Roberts + Alcee L. Hastings + Grace F. Napolitano + Herb Kohl + Ron Paul + Eddie Bernice Johnson + Leonard L. Boswell + Jerry Lewis + Edolphus Towns + Carl Levin + Orrin G. Hatch + James M. Inhofe + Richard C. Shelby + Madeleine Z. Bordallo + Dianne Feinstein + Chuck Grassley + Don Young + Richard G. Lugar + Fortney Pete Stark + Howard Coble + Sander M. Levin + C. W. Bill Young + Charles B. Rangel + Sam Johnson + Dale E. Kildee + John Conyers, Jr. + Louise McIntosh Slaughter + Roscoe G. Bartlett + John D. Dingell + Daniel K. Inouye + Daniel K. Akaka + Frank R. Lautenberg + Ralph M. Hall diff --git a/test/plots/us-congress-age-color-explicit.js b/test/plots/us-congress-age-color-explicit.js index ceafed3c86..af85848786 100644 --- a/test/plots/us-congress-age-color-explicit.js +++ b/test/plots/us-congress-age-color-explicit.js @@ -19,6 +19,7 @@ export default async function () { data, Plot.stackY2({ x: (d) => 2021 - d.birth, + stroke: "gender", fill: (d) => (d.gender === "F" ? "rgb(132, 165, 157)" : "#f6bd60"), title: "full_name" }) From 4b6aedd6243ff1f3280064e70a156887abd46f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 14 Feb 2023 17:47:22 +0100 Subject: [PATCH 02/12] document the {value, scale} pattern for color and symbol channels --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 637831f77d..3ad9f80043 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ Plot.plot({ Plot supports many scale types. Some scale types are for quantitative data: values that can be added or subtracted, such as temperature or time. Other scale types are for ordinal or categorical data: unquantifiable values that can only be ordered, such as t-shirt sizes, or values with no inherent order that can only be tested for equality, such as types of fruit. Some scale types are further intended for specific visual encodings: for example, as [position](#position-options) or [color](#color-options). -You can set the scale type explicitly via the *scale*.**type** option, though typically the scale type is inferred automatically. Some marks mandate a particular scale type: for example, [Plot.barY](#plotbarydata-options) requires that the *x* scale is a *band* scale. Some scales have a default type: for example, the *radius* scale defaults to *sqrt* and the *opacity* scale defaults to *linear*. Most often, the scale type is inferred from associated data, pulled either from the domain (if specified) or from associated channels. A *color* scale defaults to *identity* if no range or scheme is specified and all associated defined values are valid CSS color strings. Otherwise, strings and booleans imply an ordinal scale; dates imply a UTC scale; and anything else is linear. Unless they represent text, we recommend explicitly converting strings to more specific types when loading data (*e.g.*, with d3.autoType or Observable’s FileAttachment). For simplicity’s sake, Plot assumes that data is consistently typed; type inference is based solely on the first non-null, non-undefined value. +You can set the scale type explicitly via the *scale*.**type** option, though typically the scale type is inferred automatically. Some marks mandate a particular scale type: for example, [Plot.barY](#plotbarydata-options) requires that the *x* scale is a *band* scale. Some scales have a default type: for example, the *radius* scale defaults to *sqrt* and the *opacity* scale defaults to *linear*. Most often, the scale type is inferred from associated data, pulled either from the domain (if specified) or from associated channels. Strings and booleans imply an ordinal scale; dates imply a UTC scale; and anything else is linear. Unless they represent text, we recommend explicitly converting strings to more specific types when loading data (*e.g.*, with d3.autoType or Observable’s FileAttachment). For simplicity’s sake, Plot assumes that data is consistently typed; type inference is based solely on the first non-null, non-undefined value. For quantitative data (*i.e.* numbers), a mathematical transform may be applied to the data by changing the scale type: @@ -800,6 +800,12 @@ All marks support the following optional channels: The **fill**, **fillOpacity**, **stroke**, **strokeWidth**, **strokeOpacity**, and **opacity** options can be specified as either channels or constants. When the fill or stroke is specified as a function or array, it is interpreted as a channel; when the fill or stroke is specified as a string, it is interpreted as a constant if a valid CSS color and otherwise it is interpreted as a column name for a channel. Similarly when the fill opacity, stroke opacity, object opacity, stroke width, or radius is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. +Every variable color channel is scaled with the *color* scale unless its defined values are all valid CSS color strings, in which case they are used directly. This is equivalent to defining the **fill** or **stroke** channel as an object with a *value* and an undefined *scale* property. To opt out of automatic detection, specify the scale like so: +```javascript +Plot.dot(data, {stroke: {value: "fieldName", scale: "color"}}) // use the color scale +Plot.dot(data, {stroke: {value: "fieldName", scale: null}}) // directly use the given values +``` + The **title**, **href**, and **ariaLabel** options can *only* be specified as channels. When these options are specified as a string, the string refers to the name of a column in the mark’s associated data. If you’d like every instance of a particular mark to have the same value, specify the option as a function that returns the desired value, *e.g.* `() => "Hello, world!"`. The rectangular marks ([bar](#bar), [cell](#cell), [frame](#frame), and [rect](#rect)) support insets and rounded corner constant options: @@ -1461,7 +1467,7 @@ The following dot-specific constant options are also supported: The **r** option can be specified as either a channel or constant. When the radius is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. The radius defaults to 4.5 pixels when using the **symbol** channel, and otherwise 3 pixels. Dots with a nonpositive radius are not drawn. -The **stroke** defaults to none. The **fill** defaults to currentColor if the stroke is none, and to none otherwise. The **strokeWidth** defaults to 1.5. The **rotate** and **symbol** options can be specified as either channels or constants. When rotate is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. When symbol is a valid symbol name or symbol object (implementing the draw method), it is interpreted as a constant; otherwise it is interpreted as a channel. +The **stroke** defaults to none. The **fill** defaults to currentColor if the stroke is none, and to none otherwise. The **strokeWidth** defaults to 1.5. The **rotate** and **symbol** options can be specified as either channels or constants. When rotate is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. When symbol is a valid symbol name or symbol object (implementing the draw method), it is interpreted as a constant; otherwise it is interpreted as a channel. If the channel’s valid values are all symbols or symbol names, they are used directly; otherwise, the channel is bound to the *symbol* scale. The built-in **symbol** types are: *circle*, *cross*, *diamond*, *square*, *star*, *triangle*, and *wye* (for fill) and *circle*, *plus*, *times*, *triangle2*, *asterisk*, *square2*, and *diamond2* (for stroke, based on [Heman Robinson’s research](https://www.tandfonline.com/doi/abs/10.1080/10618600.2019.1637746)). The *hexagon* symbol is also supported. You can also specify a D3 or custom symbol type as an object that implements the [*symbol*.draw(*context*, *size*)](https://github.com/d3/d3-shape/blob/main/README.md#custom-symbol-types) method. From d33e69be110026a26c39d747e470b410ff0d1cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 14 Feb 2023 20:41:46 +0100 Subject: [PATCH 03/12] Update README.md Co-authored-by: Mike Bostock --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ad9f80043..9359ea6e7d 100644 --- a/README.md +++ b/README.md @@ -801,7 +801,8 @@ All marks support the following optional channels: The **fill**, **fillOpacity**, **stroke**, **strokeWidth**, **strokeOpacity**, and **opacity** options can be specified as either channels or constants. When the fill or stroke is specified as a function or array, it is interpreted as a channel; when the fill or stroke is specified as a string, it is interpreted as a constant if a valid CSS color and otherwise it is interpreted as a column name for a channel. Similarly when the fill opacity, stroke opacity, object opacity, stroke width, or radius is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. Every variable color channel is scaled with the *color* scale unless its defined values are all valid CSS color strings, in which case they are used directly. This is equivalent to defining the **fill** or **stroke** channel as an object with a *value* and an undefined *scale* property. To opt out of automatic detection, specify the scale like so: -```javascript + +```js Plot.dot(data, {stroke: {value: "fieldName", scale: "color"}}) // use the color scale Plot.dot(data, {stroke: {value: "fieldName", scale: null}}) // directly use the given values ``` From 8fce4385de1f8fe5be2668719817624e9ed18ac9 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 14 Feb 2023 13:14:50 -0800 Subject: [PATCH 04/12] const style --- src/channel.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/channel.js b/src/channel.js index 3609e4a8d1..209990d96c 100644 --- a/src/channel.js +++ b/src/channel.js @@ -46,9 +46,8 @@ export function Channels(channels, data) { export function valueObject(channels, scales) { return Object.fromEntries( Object.entries(channels).map(([name, {scale: scaleName, value}]) => { - let scale; - if (scaleName != null) scale = scales[scaleName]; - return [name, scale === undefined ? value : map(value, scale)]; + const scale = scaleName == null ? null : scales[scaleName]; + return [name, scale == null ? value : map(value, scale)]; }) ); } From 1f87e44293a4fcc45729f4dbd685e65b09cc0ea5 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 14 Feb 2023 13:20:14 -0800 Subject: [PATCH 05/12] validate scale name --- src/channel.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/channel.js b/src/channel.js index 209990d96c..7bcd40abc3 100644 --- a/src/channel.js +++ b/src/channel.js @@ -26,6 +26,8 @@ export function Channel(data, {scale, type, value, filter, hint}, name) { } } } + } else if (scale != null && !registry.has(scale)) { + throw new Error(`unknown scale: ${scale}`); } return { From a93208acd4e21142fce266b8197e4d0c37432cd3 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 14 Feb 2023 17:55:46 -0800 Subject: [PATCH 06/12] fix inferred scale for initializers --- src/channel.js | 25 +- src/plot.js | 7 +- test/output/penguinHexbinColorExplicit.svg | 289 ++++++++++++++++++++ test/plots/index.js | 1 + test/plots/penguin-hexbin-color-explicit.js | 18 ++ 5 files changed, 322 insertions(+), 18 deletions(-) create mode 100644 test/output/penguinHexbinColorExplicit.svg create mode 100644 test/plots/penguin-hexbin-color-explicit.js diff --git a/src/channel.js b/src/channel.js index 7bcd40abc3..ac1ac62192 100644 --- a/src/channel.js +++ b/src/channel.js @@ -13,19 +13,7 @@ export function Channel(data, {scale, type, value, filter, hint}, name) { // if the values are literal; however for symbols, we must promote symbol // names (e.g., "plus") to symbol implementations (symbolPlus). if (scale === "auto") { - scale = inferChannelScale(name); - if (V) { - if (scale === "color") { - if (isEvery(V, isColor)) { - scale = undefined; - } - } else if (scale === "symbol") { - if (isEvery(V, isSymbol)) { - V = map(V, maybeSymbol); - scale = undefined; - } - } - } + [scale, V] = inferChannelScale(name, V); } else if (scale != null && !registry.has(scale)) { throw new Error(`unknown scale: ${scale}`); } @@ -55,16 +43,19 @@ export function valueObject(channels, scales) { } // Returns the default scale for the channel with the given name. -export function inferChannelScale(name) { +export function inferChannelScale(name, value) { switch (name) { case "fill": case "stroke": - return "color"; + case "color": + return [isEvery(value, isColor) ? null : "color", value]; case "fillOpacity": case "strokeOpacity": - return "opacity"; + return ["opacity", value]; + case "symbol": + return isEvery(value, isSymbol) ? [null, map(value, maybeSymbol)] : ["symbol", value]; } - return registry.has(name) ? name : null; + return [registry.has(name) ? name : null, value]; } // Note: mutates channel.domain! This is set to a function so that it is lazily diff --git a/src/plot.js b/src/plot.js index e9bf3f2973..2c302a44a7 100644 --- a/src/plot.js +++ b/src/plot.js @@ -373,7 +373,12 @@ function applyScaleTransform(channel, options) { function inferChannelScales(channels) { for (const name in channels) { const channel = channels[name]; - if (channel.scale === true) channel.scale = inferChannelScale(name); + const {scale, value} = channel; + if (scale === true || scale === "auto") { + [channel.scale, channel.value] = inferChannelScale(name, value); + } else if (scale != null && !scaleRegistry.has(scale)) { + throw new Error(`unknown scale: ${scale}`); + } } } diff --git a/test/output/penguinHexbinColorExplicit.svg b/test/output/penguinHexbinColorExplicit.svg new file mode 100644 index 0000000000..4b0a80bdd0 --- /dev/null +++ b/test/output/penguinHexbinColorExplicit.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 34 + 36 + 38 + 40 + 42 + 44 + 46 + 48 + 50 + 52 + 54 + 56 + 58 + + + ↑ culmen_length_mm + + + + + + + + + + + + + + + + + + + + + + + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + + + culmen_depth_mm → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index 12f400d46b..cd6df3459d 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -295,6 +295,7 @@ export * from "./legend-opacity.js"; export * from "./legend-symbol.js"; export * from "./libor-projections.js"; export * from "./long-labels.js"; +export * from "./penguin-hexbin-color-explicit.js"; export * from "./raster-ca55.js"; export * from "./raster-penguins.js"; export * from "./raster-vapor.js"; diff --git a/test/plots/penguin-hexbin-color-explicit.js b/test/plots/penguin-hexbin-color-explicit.js new file mode 100644 index 0000000000..4fbe33df7f --- /dev/null +++ b/test/plots/penguin-hexbin-color-explicit.js @@ -0,0 +1,18 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export async function penguinHexbinColorExplicit() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + grid: true, + marks: [ + Plot.dot( + penguins, + Plot.hexbin( + {r: "count", fill: (d) => (d.length > 3 ? "green" : "yellow")}, + {x: "culmen_depth_mm", y: "culmen_length_mm"} + ) + ) + ] + }); +} From 6e5f2ea8ce679f89a118394b67ea26a89b216cf9 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 14 Feb 2023 19:24:54 -0800 Subject: [PATCH 07/12] inferChannelScale --- src/channel.js | 64 ++++++++++++++++++++++++++++---------------------- src/plot.js | 8 +------ 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/channel.js b/src/channel.js index ac1ac62192..fc4fa63843 100644 --- a/src/channel.js +++ b/src/channel.js @@ -6,26 +6,14 @@ import {maybeReduce} from "./transforms/group.js"; // TODO Type coercion? export function Channel(data, {scale, type, value, filter, hint}, name) { - let V = valueof(data, value); - - // If the channel uses the "auto" scale, infer the scale from the channel name - // and the provided values. For color and symbol channels, no scale is applied - // if the values are literal; however for symbols, we must promote symbol - // names (e.g., "plus") to symbol implementations (symbolPlus). - if (scale === "auto") { - [scale, V] = inferChannelScale(name, V); - } else if (scale != null && !registry.has(scale)) { - throw new Error(`unknown scale: ${scale}`); - } - - return { + return inferChannelScale(name, { scale, type, - value: V, + value: valueof(data, value), label: labelof(value), filter, hint - }; + }); } export function Channels(channels, data) { @@ -42,20 +30,40 @@ export function valueObject(channels, scales) { ); } -// Returns the default scale for the channel with the given name. -export function inferChannelScale(name, value) { - switch (name) { - case "fill": - case "stroke": - case "color": - return [isEvery(value, isColor) ? null : "color", value]; - case "fillOpacity": - case "strokeOpacity": - return ["opacity", value]; - case "symbol": - return isEvery(value, isSymbol) ? [null, map(value, maybeSymbol)] : ["symbol", value]; +// If the channel uses the "auto" scale (or equivalently true), infer the scale +// from the channel name and the provided values. For color and symbol channels, +// no scale is applied if the values are literal; however for symbols, we must +// promote symbol names (e.g., "plus") to symbol implementations (symbolPlus). +// Note: mutates channel! +export function inferChannelScale(name, channel) { + const {scale, value} = channel; + if (scale === true || scale === "auto") { + switch (name) { + case "fill": + case "stroke": + case "color": + channel.scale = isEvery(value, isColor) ? null : "color"; + break; + case "fillOpacity": + case "strokeOpacity": + channel.scale = "opacity"; + break; + case "symbol": + if (isEvery(value, isSymbol)) { + channel.scale = null; + channel.value = map(value, maybeSymbol); + } else { + channel.scale = "symbol"; + } + break; + default: + channel.scale = registry.has(name) ? name : null; + break; + } + } else if (scale != null && !registry.has(scale)) { + throw new Error(`unknown scale: ${scale}`); } - return [registry.has(name) ? name : null, value]; + return channel; } // Note: mutates channel.domain! This is set to a function so that it is lazily diff --git a/src/plot.js b/src/plot.js index 2c302a44a7..39d3311785 100644 --- a/src/plot.js +++ b/src/plot.js @@ -372,13 +372,7 @@ function applyScaleTransform(channel, options) { // channels, but here we assume common behavior across marks. function inferChannelScales(channels) { for (const name in channels) { - const channel = channels[name]; - const {scale, value} = channel; - if (scale === true || scale === "auto") { - [channel.scale, channel.value] = inferChannelScale(name, value); - } else if (scale != null && !scaleRegistry.has(scale)) { - throw new Error(`unknown scale: ${scale}`); - } + inferChannelScale(name, channels[name]); } } From 023ea9fe0cb20fcfed264d51b1e0507ed476c130 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 14 Feb 2023 19:27:39 -0800 Subject: [PATCH 08/12] less painful colors --- test/output/penguinHexbinColorExplicit.svg | 380 ++++++++++---------- test/plots/penguin-hexbin-color-explicit.js | 2 +- 2 files changed, 191 insertions(+), 191 deletions(-) diff --git a/test/output/penguinHexbinColorExplicit.svg b/test/output/penguinHexbinColorExplicit.svg index 4b0a80bdd0..239d7d8901 100644 --- a/test/output/penguinHexbinColorExplicit.svg +++ b/test/output/penguinHexbinColorExplicit.svg @@ -95,195 +95,195 @@ culmen_depth_mm → - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/penguin-hexbin-color-explicit.js b/test/plots/penguin-hexbin-color-explicit.js index 4fbe33df7f..f0dd0809ef 100644 --- a/test/plots/penguin-hexbin-color-explicit.js +++ b/test/plots/penguin-hexbin-color-explicit.js @@ -9,7 +9,7 @@ export async function penguinHexbinColorExplicit() { Plot.dot( penguins, Plot.hexbin( - {r: "count", fill: (d) => (d.length > 3 ? "green" : "yellow")}, + {r: "count", fill: (d) => (d.length > 3 ? "black" : "red")}, {x: "culmen_depth_mm", y: "culmen_length_mm"} ) ) From ed0c04928ca9cae2fd9ad8eb0b563b6c54854b77 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 15 Feb 2023 11:13:16 -0500 Subject: [PATCH 09/12] Update README.md --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9359ea6e7d..534c39d628 100644 --- a/README.md +++ b/README.md @@ -800,13 +800,20 @@ All marks support the following optional channels: The **fill**, **fillOpacity**, **stroke**, **strokeWidth**, **strokeOpacity**, and **opacity** options can be specified as either channels or constants. When the fill or stroke is specified as a function or array, it is interpreted as a channel; when the fill or stroke is specified as a string, it is interpreted as a constant if a valid CSS color and otherwise it is interpreted as a column name for a channel. Similarly when the fill opacity, stroke opacity, object opacity, stroke width, or radius is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. -Every variable color channel is scaled with the *color* scale unless its defined values are all valid CSS color strings, in which case they are used directly. This is equivalent to defining the **fill** or **stroke** channel as an object with a *value* and an undefined *scale* property. To opt out of automatic detection, specify the scale like so: +The scale associated with any channel can be overridden by specifying the channel as an object with a *value* property specifying the channel values and a *scale* property specifying the desired scale name or null for an unscaled channel. For example, to force the **stroke** channel to be unscaled, interpreting the associated values as literal color strings: ```js -Plot.dot(data, {stroke: {value: "fieldName", scale: "color"}}) // use the color scale -Plot.dot(data, {stroke: {value: "fieldName", scale: null}}) // directly use the given values +Plot.dot(data, {stroke: {value: "fieldName", scale: null}}) ``` +To instead force the **stroke** channel to be bound to the *color* scale regardless of the provided values, say: + +```js +Plot.dot(data, {stroke: {value: "fieldName", scale: "color"}}) +``` + +The color channels (**fill** and **stroke**) are bound to the *color* scale by default, unless the provided values are all valid CSS color strings or nullish, in which case the values are interpreted literally and unscaled. + The **title**, **href**, and **ariaLabel** options can *only* be specified as channels. When these options are specified as a string, the string refers to the name of a column in the mark’s associated data. If you’d like every instance of a particular mark to have the same value, specify the option as a function that returns the desired value, *e.g.* `() => "Hello, world!"`. The rectangular marks ([bar](#bar), [cell](#cell), [frame](#frame), and [rect](#rect)) support insets and rounded corner constant options: From ff84387e8abf5575bafa528da3f36efee95309e4 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 15 Feb 2023 11:14:16 -0500 Subject: [PATCH 10/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 534c39d628..993d550e4c 100644 --- a/README.md +++ b/README.md @@ -1475,7 +1475,7 @@ The following dot-specific constant options are also supported: The **r** option can be specified as either a channel or constant. When the radius is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. The radius defaults to 4.5 pixels when using the **symbol** channel, and otherwise 3 pixels. Dots with a nonpositive radius are not drawn. -The **stroke** defaults to none. The **fill** defaults to currentColor if the stroke is none, and to none otherwise. The **strokeWidth** defaults to 1.5. The **rotate** and **symbol** options can be specified as either channels or constants. When rotate is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. When symbol is a valid symbol name or symbol object (implementing the draw method), it is interpreted as a constant; otherwise it is interpreted as a channel. If the channel’s valid values are all symbols or symbol names, they are used directly; otherwise, the channel is bound to the *symbol* scale. +The **stroke** defaults to none. The **fill** defaults to currentColor if the stroke is none, and to none otherwise. The **strokeWidth** defaults to 1.5. The **rotate** and **symbol** options can be specified as either channels or constants. When rotate is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. When symbol is a valid symbol name or symbol object (implementing the draw method), it is interpreted as a constant; otherwise it is interpreted as a channel. If the channel’s valid values are all symbols or symbol names or nullish, they are unscaled (interpreted literally); otherwise, the channel is bound to the *symbol* scale. The built-in **symbol** types are: *circle*, *cross*, *diamond*, *square*, *star*, *triangle*, and *wye* (for fill) and *circle*, *plus*, *times*, *triangle2*, *asterisk*, *square2*, and *diamond2* (for stroke, based on [Heman Robinson’s research](https://www.tandfonline.com/doi/abs/10.1080/10618600.2019.1637746)). The *hexagon* symbol is also supported. You can also specify a D3 or custom symbol type as an object that implements the [*symbol*.draw(*context*, *size*)](https://github.com/d3/d3-shape/blob/main/README.md#custom-symbol-types) method. From 1eafabfd1e6a05a562a2d62324a957e4a3ab22f1 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 15 Feb 2023 11:15:31 -0500 Subject: [PATCH 11/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 993d550e4c..2505a12f01 100644 --- a/README.md +++ b/README.md @@ -1475,7 +1475,7 @@ The following dot-specific constant options are also supported: The **r** option can be specified as either a channel or constant. When the radius is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. The radius defaults to 4.5 pixels when using the **symbol** channel, and otherwise 3 pixels. Dots with a nonpositive radius are not drawn. -The **stroke** defaults to none. The **fill** defaults to currentColor if the stroke is none, and to none otherwise. The **strokeWidth** defaults to 1.5. The **rotate** and **symbol** options can be specified as either channels or constants. When rotate is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. When symbol is a valid symbol name or symbol object (implementing the draw method), it is interpreted as a constant; otherwise it is interpreted as a channel. If the channel’s valid values are all symbols or symbol names or nullish, they are unscaled (interpreted literally); otherwise, the channel is bound to the *symbol* scale. +The **stroke** defaults to none. The **fill** defaults to currentColor if the stroke is none, and to none otherwise. The **strokeWidth** defaults to 1.5. The **rotate** and **symbol** options can be specified as either channels or constants. When rotate is specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. When symbol is a valid symbol name or symbol object (implementing the draw method), it is interpreted as a constant; otherwise it is interpreted as a channel. If the **symbol** channel’s values are all symbols, symbol names, or nullish, the channel is unscaled (values are interpreted literally); otherwise, the channel is bound to the *symbol* scale. The built-in **symbol** types are: *circle*, *cross*, *diamond*, *square*, *star*, *triangle*, and *wye* (for fill) and *circle*, *plus*, *times*, *triangle2*, *asterisk*, *square2*, and *diamond2* (for stroke, based on [Heman Robinson’s research](https://www.tandfonline.com/doi/abs/10.1080/10618600.2019.1637746)). The *hexagon* symbol is also supported. You can also specify a D3 or custom symbol type as an object that implements the [*symbol*.draw(*context*, *size*)](https://github.com/d3/d3-shape/blob/main/README.md#custom-symbol-types) method. From b6ca9530b21b5b25b2a6ff61c44c19c95cf0514f Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 15 Feb 2023 08:26:23 -0800 Subject: [PATCH 12/12] Update README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 2505a12f01..d387cf966a 100644 --- a/README.md +++ b/README.md @@ -814,6 +814,12 @@ Plot.dot(data, {stroke: {value: "fieldName", scale: "color"}}) The color channels (**fill** and **stroke**) are bound to the *color* scale by default, unless the provided values are all valid CSS color strings or nullish, in which case the values are interpreted literally and unscaled. +In addition to functions of data, arrays, and column names, channel values can be specified as an object with a *transform* method; this transform method is passed the mark’s array of data and must return the corresponding array of channel values. (Whereas a channel value specified as a function is invoked repeatedly for each element in the mark’s data, similar to *array*.map, the transform method is invoked only once being passed the entire array of data.) For example, to pass the mark’s data directly to the **x** channel, equivalent to [Plot.identity](#plotidentity): + +```js +Plot.dot(numbers, {x: {transform: data => data}}) +``` + The **title**, **href**, and **ariaLabel** options can *only* be specified as channels. When these options are specified as a string, the string refers to the name of a column in the mark’s associated data. If you’d like every instance of a particular mark to have the same value, specify the option as a function that returns the desired value, *e.g.* `() => "Hello, world!"`. The rectangular marks ([bar](#bar), [cell](#cell), [frame](#frame), and [rect](#rect)) support insets and rounded corner constant options: