diff --git a/src/mark.js b/src/mark.js index 86039ad5f62..95a82529b6f 100644 --- a/src/mark.js +++ b/src/mark.js @@ -136,16 +136,16 @@ const colors = new Set(["currentColor", "none"]); // tuple [channel, constant] where one of the two is undefined, and the other is // the given value. If you wish to reference a named field that is also a valid // CSS color, use an accessor (d => d.red) instead. -export function maybeColor(value, defaultValue) { +export function maybeColorChannel(value, defaultValue) { if (value === undefined) value = defaultValue; return value === null ? [undefined, "none"] : typeof value === "string" && (colors.has(value) || color(value)) ? [undefined, value] : [value, undefined]; } -// Similar to maybeColor, this tests whether the given value is a number +// Similar to maybeColorChannel, this tests whether the given value is a number // indicating a constant, and otherwise assumes that it’s a channel value. -export function maybeNumber(value, defaultValue) { +export function maybeNumberChannel(value, defaultValue) { if (value === undefined) value = defaultValue; return value === null || typeof value === "number" ? [undefined, value] : [value, undefined]; @@ -204,8 +204,8 @@ export function maybeTuple(x, y) { // A helper for extracting the z channel, if it is variable. Used by transforms // that require series, such as moving average and normalize. export function maybeZ({z, fill, stroke} = {}) { - if (z === undefined) ([z] = maybeColor(fill)); - if (z === undefined) ([z] = maybeColor(stroke)); + if (z === undefined) ([z] = maybeColorChannel(fill)); + if (z === undefined) ([z] = maybeColorChannel(stroke)); return z; } diff --git a/src/marks/cell.js b/src/marks/cell.js index 39611ed5738..04acb9cdd2a 100644 --- a/src/marks/cell.js +++ b/src/marks/cell.js @@ -1,4 +1,4 @@ -import {identity, indexOf, maybeColor, maybeTuple} from "../mark.js"; +import {identity, indexOf, maybeColorChannel, maybeTuple} from "../mark.js"; import {AbstractBar} from "./bar.js"; export class Cell extends AbstractBar { @@ -26,11 +26,11 @@ export function cell(data, {x, y, ...options} = {}) { } export function cellX(data, {x = indexOf, fill, stroke, ...options} = {}) { - if (fill === undefined && maybeColor(stroke)[0] === undefined) fill = identity; + if (fill === undefined && maybeColorChannel(stroke)[0] === undefined) fill = identity; return new Cell(data, {...options, x, fill, stroke}); } export function cellY(data, {y = indexOf, fill, stroke, ...options} = {}) { - if (fill === undefined && maybeColor(stroke)[0] === undefined) fill = identity; + if (fill === undefined && maybeColorChannel(stroke)[0] === undefined) fill = identity; return new Cell(data, {...options, y, fill, stroke}); } diff --git a/src/marks/dot.js b/src/marks/dot.js index 6df80155ad4..5e125d9e02c 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.js @@ -1,7 +1,7 @@ import {create, path, symbolCircle} from "d3"; import {filter, positive} from "../defined.js"; -import {Mark, identity, maybeNumber, maybeTuple} from "../mark.js"; -import {maybeSymbol} from "../scales/symbol.js"; +import {Mark, identity, maybeNumberChannel, maybeTuple} from "../mark.js"; +import {maybeSymbolChannel} from "../scales/symbol.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js"; const defaults = { @@ -13,9 +13,9 @@ const defaults = { export class Dot extends Mark { constructor(data, options = {}) { const {x, y, r, rotate, symbol} = options; - const [vr, cr] = maybeNumber(r, 3); - const [vrotate, crotate] = maybeNumber(rotate, 0); - const [vsymbol, csymbol] = maybeSymbol(symbol); + const [vr, cr] = maybeNumberChannel(r, 3); + const [vrotate, crotate] = maybeNumberChannel(rotate, 0); + const [vsymbol, csymbol] = maybeSymbolChannel(symbol); super( data, [ diff --git a/src/marks/image.js b/src/marks/image.js index 302b59b535a..dd037de4501 100644 --- a/src/marks/image.js +++ b/src/marks/image.js @@ -1,6 +1,6 @@ import {create} from "d3"; import {filter, positive} from "../defined.js"; -import {Mark, maybeNumber, maybeTuple, string} from "../mark.js"; +import {Mark, maybeNumberChannel, maybeTuple, string} from "../mark.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, applyAttr, offset, impliedString} from "../style.js"; const defaults = { @@ -24,7 +24,7 @@ function isUrl(string) { // Disambiguates a constant src definition from a channel. A path or URL string // is assumed to be a constant; any other string is assumed to be a field name. -function maybePath(value) { +function maybePathChannel(value) { return typeof value === "string" && (isPath(value) || isUrl(value)) ? [undefined, value] : [value, undefined]; @@ -35,9 +35,9 @@ export class Image extends Mark { let {x, y, width, height, src, preserveAspectRatio, crossOrigin} = options; if (width === undefined && height !== undefined) width = height; else if (height === undefined && width !== undefined) height = width; - const [vs, cs] = maybePath(src); - const [vw, cw] = maybeNumber(width, 16); - const [vh, ch] = maybeNumber(height, 16); + const [vs, cs] = maybePathChannel(src); + const [vw, cw] = maybeNumberChannel(width, 16); + const [vh, ch] = maybeNumberChannel(height, 16); super( data, [ diff --git a/src/marks/text.js b/src/marks/text.js index e0a3a748b31..b8e3a23559b 100644 --- a/src/marks/text.js +++ b/src/marks/text.js @@ -1,6 +1,6 @@ import {create} from "d3"; import {filter, nonempty} from "../defined.js"; -import {Mark, indexOf, identity, string, maybeNumber, maybeTuple, numberChannel} from "../mark.js"; +import {Mark, indexOf, identity, string, maybeNumberChannel, maybeTuple, numberChannel} from "../mark.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyAttr, applyTransform, offset} from "../style.js"; const defaults = { @@ -23,8 +23,8 @@ export class Text extends Mark { dy = "0.32em", rotate } = options; - const [vrotate, crotate] = maybeNumber(rotate, 0); - const [vfontSize, cfontSize] = maybeNumber(fontSize); + const [vrotate, crotate] = maybeNumberChannel(rotate, 0); + const [vfontSize, cfontSize] = maybeNumberChannel(fontSize); super( data, [ diff --git a/src/marks/vector.js b/src/marks/vector.js index 741389f87af..20cd0b14ea7 100644 --- a/src/marks/vector.js +++ b/src/marks/vector.js @@ -1,6 +1,6 @@ import {create} from "d3"; import {filter} from "../defined.js"; -import {Mark, identity, maybeNumber, maybeTuple, keyword} from "../mark.js"; +import {Mark, identity, maybeNumberChannel, maybeTuple, keyword} from "../mark.js"; import {radians} from "../math.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js"; @@ -14,8 +14,8 @@ const defaults = { export class Vector extends Mark { constructor(data, options = {}) { const {x, y, length, rotate, anchor = "middle"} = options; - const [vl, cl] = maybeNumber(length, 12); - const [vr, cr] = maybeNumber(rotate, 0); + const [vl, cl] = maybeNumberChannel(length, 12); + const [vr, cr] = maybeNumberChannel(rotate, 0); super( data, [ diff --git a/src/scales/ordinal.js b/src/scales/ordinal.js index 37c2ceed84a..33a205ae668 100644 --- a/src/scales/ordinal.js +++ b/src/scales/ordinal.js @@ -3,6 +3,7 @@ import {scaleBand, scaleOrdinal, scalePoint, scaleImplicit} from "d3"; import {ordinalScheme, quantitativeScheme} from "./schemes.js"; import {ascendingDefined} from "../defined.js"; import {registry, color, symbol} from "./index.js"; +import {maybeSymbol} from "./symbol.js"; export function ScaleO(scale, channels, { type, @@ -23,12 +24,14 @@ export function ScaleO(scale, channels, { export function ScaleOrdinal(key, channels, { type, - range = registry.get(key) === symbol ? symbols : undefined, // TODO map symbol names to implementations + range, scheme = range === undefined ? type === "ordinal" ? "turbo" : "tableau10" : undefined, unknown, ...options }) { - if (registry.get(key) === color && scheme !== undefined) { + if (registry.get(key) === symbol) { + range = range === undefined ? symbols : Array.from(range, maybeSymbol); + } else if (registry.get(key) === color && scheme !== undefined) { if (range !== undefined) { const interpolate = quantitativeScheme(scheme); const t0 = range[0], d = range[1] - range[0]; diff --git a/src/scales/symbol.js b/src/scales/symbol.js index f54d5a7b60a..96fc2089da9 100644 --- a/src/scales/symbol.js +++ b/src/scales/symbol.js @@ -14,9 +14,15 @@ function isSymbol(symbol) { return symbol && typeof symbol.draw === "function"; } -export function maybeSymbol(symbol = symbolCircle) { - if (symbol == null) return [undefined, null]; - if (isSymbol(symbol)) return [undefined, symbol]; +export function maybeSymbol(symbol) { + if (symbol == null || isSymbol(symbol)) return symbol; + const value = symbols.get(`${symbol}`.toLowerCase()); + if (value) return value; + throw new Error(`invalid symbol: ${symbol}`); +} + +export function maybeSymbolChannel(symbol = symbolCircle) { + if (symbol == null || isSymbol(symbol)) return [undefined, symbol]; if (typeof symbol === "string") { const value = symbols.get(`${symbol}`.toLowerCase()); if (value) return [undefined, value]; diff --git a/src/style.js b/src/style.js index cdb056e2821..4c55be9769b 100644 --- a/src/style.js +++ b/src/style.js @@ -1,5 +1,5 @@ import {namespaces} from "d3"; -import {string, number, maybeColor, maybeNumber, title, titleGroup} from "./mark.js"; +import {string, number, maybeColorChannel, maybeNumberChannel, title, titleGroup} from "./mark.js"; import {filter} from "./defined.js"; export const offset = typeof window !== "undefined" && window.devicePixelRatio > 1 ? 0 : 0.5; @@ -58,11 +58,11 @@ export function styles( if (none(defaultStroke) && !none(stroke)) defaultFill = "none"; } - const [vfill, cfill] = maybeColor(fill, defaultFill); - const [vfillOpacity, cfillOpacity] = maybeNumber(fillOpacity); - const [vstroke, cstroke] = maybeColor(stroke, defaultStroke); - const [vstrokeOpacity, cstrokeOpacity] = maybeNumber(strokeOpacity); - const [vopacity, copacity] = maybeNumber(opacity); + const [vfill, cfill] = maybeColorChannel(fill, defaultFill); + const [vfillOpacity, cfillOpacity] = maybeNumberChannel(fillOpacity); + const [vstroke, cstroke] = maybeColorChannel(stroke, defaultStroke); + const [vstrokeOpacity, cstrokeOpacity] = maybeNumberChannel(strokeOpacity); + const [vopacity, copacity] = maybeNumberChannel(opacity); // For styles that have no effect if there is no stroke, only apply the // defaults if the stroke is not (constant) none. @@ -73,7 +73,7 @@ export function styles( if (strokeMiterlimit === undefined) strokeMiterlimit = defaultStrokeMiterlimit; } - const [vstrokeWidth, cstrokeWidth] = maybeNumber(strokeWidth); + const [vstrokeWidth, cstrokeWidth] = maybeNumberChannel(strokeWidth); // Some marks don’t support fill (e.g., tick and rule). if (defaultFill !== null) { diff --git a/src/transforms/bin.js b/src/transforms/bin.js index 4e87b89ad4d..e080e0d7931 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -1,5 +1,5 @@ import {bin as binner, extent, thresholdFreedmanDiaconis, thresholdScott, thresholdSturges, utcTickInterval} from "d3"; -import {valueof, range, identity, maybeLazyChannel, maybeTuple, maybeColor, maybeValue, mid, labelof, isTemporal} from "../mark.js"; +import {valueof, range, identity, maybeLazyChannel, maybeTuple, maybeColorChannel, maybeValue, mid, labelof, isTemporal} from "../mark.js"; import {coerceDate} from "../scales.js"; import {basic} from "./basic.js"; import {hasOutput, maybeEvaluator, maybeGroup, maybeOutput, maybeOutputs, maybeReduce, maybeSort, maybeSubgroup, reduceCount, reduceIdentity} from "./group.js"; @@ -81,8 +81,8 @@ function binn( ...options } = inputs; const [GZ, setGZ] = maybeLazyChannel(z); - const [vfill] = maybeColor(fill); - const [vstroke] = maybeColor(stroke); + const [vfill] = maybeColorChannel(fill); + const [vstroke] = maybeColorChannel(stroke); const [GF = fill, setGF] = maybeLazyChannel(vfill); const [GS = stroke, setGS] = maybeLazyChannel(vstroke); diff --git a/src/transforms/group.js b/src/transforms/group.js index d61628e9928..fbc3a006485 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -1,6 +1,6 @@ import {group as grouper, sort, sum, deviation, min, max, mean, median, mode, variance, InternSet, minIndex, maxIndex} from "d3"; import {ascendingDefined, firstof} from "../defined.js"; -import {valueof, maybeColor, maybeInput, maybeTuple, maybeLazyChannel, lazyChannel, first, identity, take, labelof, range} from "../mark.js"; +import {valueof, maybeColorChannel, maybeInput, maybeTuple, maybeLazyChannel, lazyChannel, first, identity, take, labelof, range} from "../mark.js"; import {basic} from "./basic.js"; // Group on {z, fill, stroke}. @@ -66,8 +66,8 @@ function groupn( ...options } = inputs; const [GZ, setGZ] = maybeLazyChannel(z); - const [vfill] = maybeColor(fill); - const [vstroke] = maybeColor(stroke); + const [vfill] = maybeColorChannel(fill); + const [vstroke] = maybeColorChannel(stroke); const [GF = fill, setGF] = maybeLazyChannel(vfill); const [GS = stroke, setGS] = maybeLazyChannel(vstroke);