diff --git a/src/data.ts b/src/data.ts index 15f05d1923..183ab4ccb8 100644 --- a/src/data.ts +++ b/src/data.ts @@ -1,16 +1,14 @@ -type ObjectDatum = Record; -export type Datum = ObjectDatum | Value; -export type DatumKeys = T extends ObjectDatum ? keyof T : never; - /** * The marks's data contains the data for the mark; typically an array * of objects or values, but can also be defined as an iterable compatible * with Array.from. */ export type Data = ArrayLike | Iterable; +export type Datum = unknown; /** * An array or typed array constructor, or any class that implements Array.from + * We only do internal typechecks for Float32Array | Float64Array */ export type ArrayType = ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor; diff --git a/src/options.ts b/src/options.ts index 9e4fe9e8a7..32e66f21b6 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type {ArrayType, Value, DataArray, Datum, index, Data, Series, ValueArray, DatumKeys} from "./data.js"; +import type {ArrayType, Value, DataArray, Datum, index, Data, Series, ValueArray} from "./data.js"; import {parse as isoParse} from "isoformat"; import {color, descending, quantile, TypedArray} from "d3"; @@ -54,6 +54,10 @@ export function valueof( value: ValueAccessor, arrayType?: ArrayType ): ValueArray | Float32Array | Float64Array | null | undefined { + if (isTransform(value)) { + data = value.transform(data) as Data | null | undefined; + value = (d) => d as Value; + } if (data == null) { return data; } else if (typeof value === "string") { @@ -62,32 +66,31 @@ export function valueof( return map(data, value, arrayType); } else if (typeof value === "number" || value instanceof Date || typeof value === "boolean") { return map(data, constant(value), arrayType); - } else if (isTransform(value)) { - return arrayify(value.transform(data), arrayType); // Not sure what's wrong here } - return arrayify(value, arrayType); // preserve undefined type + return arrayify(value as ValueArray, arrayType); // preserve undefined type } -function isTransform(value: ValueAccessor): value is TransformMethod { - return !!value && typeof value == "object" && "function" in value; +function isTransform(value: ValueAccessor): value is TransformMethod { + return !!value && isObject(value) && typeof (value as {transform?: any}).transform == "function"; } /** * See Plot.valueof() */ +type DatumKeys = T extends Record ? keyof T : never; export type ValueAccessor = | DatumKeys | AccessorFunction | number | Date | boolean - | TransformMethod - // | ValueArray // This didn't seem right, how would we use ValueArray as an accessor? + | TransformMethod + | ValueArray | null | undefined; type AccessorFunction = ((d: T) => Value) & ((d: T, i: number) => Value); -type TransformMethod = { - transform: ((data: Data) => Data) & ((data: null) => null) & ((data: undefined) => undefined); +type TransformMethod = { + transform: (data: Data | null | undefined) => Data | null | undefined; }; // Type: the field accessor might crash if the datum is not a generic object @@ -130,15 +133,15 @@ export function percentile(reduce: percentile) { // 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 maybeColorChannel( - value: ValueAccessor, - defaultValue?: DatumKeys + value: string | ValueAccessor, + defaultValue?: string ): [ValueAccessor?, string?] { if (value === undefined) value = defaultValue; return value === null ? [undefined, "none"] - : isColor(value) - ? [undefined, value as DatumKeys] - : [value, undefined]; + : isColor(value as string) + ? [undefined, value as string] + : [value as ValueAccessor, undefined]; } // Similar to maybeColorChannel, this tests whether the given value is a number @@ -170,26 +173,11 @@ export function keyword(input: string | null | undefined, name: string, allowed: export function arrayify(data: undefined, type: ArrayType | undefined): undefined; export function arrayify(data: null, type: ArrayType | undefined): null; export function arrayify(data: null | undefined, type: ArrayType | undefined): null | undefined; -export function arrayify(data: Data): Array | TypedArray; -export function arrayify(data: Data, type: ArrayConstructor): Array; +export function arrayify(data: Data): Array | TypedArray; +export function arrayify(data: Data, type: ArrayConstructor): Array; export function arrayify(data: Data, type: Float32ArrayConstructor): Float32Array; export function arrayify(data: Data, type: Float64ArrayConstructor): Float64Array; -export function arrayify(data: Data, type?: ArrayType): Array | Float32Array | Float64Array; -export function arrayify( - data: Data | null | undefined -): Array | TypedArray | null | undefined; -export function arrayify( - data: Data | null | undefined, - type: ArrayConstructor -): Array | null | undefined; -export function arrayify( - data: Data | null | undefined, - type: Float32ArrayConstructor -): Float32Array | null | undefined; -export function arrayify( - data: Data | null | undefined, - type: Float64ArrayConstructor -): Float64Array | null | undefined; +export function arrayify(data: Data, type?: ArrayType): Array | Float32Array | Float64Array; export function arrayify( data: Data | null | undefined, type?: ArrayType @@ -197,8 +185,10 @@ export function arrayify( return data == null ? data : type === undefined - ? data instanceof Array || data instanceof TypedArray - ? (data as T[]) // is this change from ValueArray to T[] correct? + ? data instanceof Array + ? data + : data instanceof TypedArray + ? (data as TypedArray) : Array.from(data) : data instanceof type ? data @@ -207,6 +197,39 @@ export function arrayify( // An optimization of type.from(values, f): if the given values are already an // instanceof the desired array type, the faster values.map method is used. +export function map(values: ValueArray, f: (d: Value, i: number) => Value): ValueArray; +export function map( + values: ValueArray, + f: (d: any, i: number) => Value, + type: ArrayConstructor | undefined +): ValueArray; +export function map( + values: ValueArray, + f: (d: Value, i: number) => number, + type: Float32ArrayConstructor +): Float32Array; +export function map( + values: ValueArray, + f: (d: Value, i: number) => number, + type: Float64ArrayConstructor +): Float64Array; +export function map( + values: Data, + f: (d: any, i: number) => number, + type: Float32ArrayConstructor +): Float32Array; +export function map( + values: Data, + f: (d: any, i: number) => number, + type: Float64ArrayConstructor +): Float64Array; +export function map(values: Data, f: AccessorFunction, type: ArrayType | undefined): Value[]; +export function map(values: Data, f: AccessorFunction): Value[]; +export function map( + values: Data, + f: (d: any, i: number) => any, + type: ArrayType | undefined +): Value[]; export function map( values: Data, f: (d: any, i: number) => V, @@ -229,7 +252,11 @@ export function map( f: (d: any, i: number) => any, type: ArrayType | undefined ): Value[]; -export function map(values: Data, f: (d: any, i: number) => any, type: ArrayType = Array) { +export function map( + values: Data | Data, + f: (d: any, i: number) => any, + type: ArrayType = Array +): ValueArray { return values instanceof type ? values.map(f) : (type as ArrayConstructor).from(values, f); } @@ -278,7 +305,7 @@ export function maybeZero( x: ValueAccessor | undefined, x1: ValueAccessor | undefined | 0, x2: ValueAccessor | undefined | Identity | 0, - x3 = identity + x3 = identity as Identity ) { if (x1 === undefined && x2 === undefined) { // {x} or {} @@ -302,7 +329,11 @@ export function maybeTuple(x: T | undefined, y: T | undefined): [T | undefine // A helper for extracting the z channel, if it is variable. Used by transforms // that require series, such as moving average and normalize. -type ZOptions = {fill?: ValueAccessor; stroke?: ValueAccessor; z?: ValueAccessor}; +type ZOptions = { + fill?: ValueAccessor | string; + stroke?: ValueAccessor | string; + z?: ValueAccessor; +}; export function maybeZ({z, fill, stroke}: ZOptions = {}) { if (z === undefined) [z] = maybeColorChannel(fill); if (z === undefined) [z] = maybeColorChannel(stroke); @@ -378,9 +409,12 @@ export function maybeColumn( return source == null ? [source] : column(source); } -type MaybeLabel = string | {label: string} | null | undefined; -export function labelof(value: MaybeLabel, defaultValue?: string) { - return typeof value === "string" ? value : value && "label" in value ? value.label : defaultValue; +export function labelof(value: any, defaultValue?: string) { + return typeof value === "string" + ? value + : value && (value as {label: string}).label != null + ? (value as {label: string}).label + : defaultValue; } // Assuming that both x1 and x2 and lazy columns (per above), this derives a new @@ -393,8 +427,8 @@ export function mid(x1: getColumn, x2: getColumn) { const X1 = x1.transform(); const X2 = x2.transform(); return isTemporal(X1) || isTemporal(X2) - ? map(X1, (_: Date, i: index) => new Date((+(X1[i] as number) + +(X2[i] as number)) / 2)) - : map(X1, (_: Date, i: index) => (+(X1[i] as number) + +(X2[i] as number)) / 2, Float64Array); + ? map(X1, (_, i) => new Date((+(X1[i] as number) + +(X2[i] as number)) / 2)) + : map(X1, (_, i) => (+(X1[i] as number) + +(X2[i] as number)) / 2, Float64Array); }, label: x1.label }; @@ -408,7 +442,7 @@ export function maybeValue(value: any) { // Coerces the given channel values (if any) to numbers. This is useful when // values will be interpolated into other code, such as an SVG transform, and // where we don’t wish to allow unexpected behavior for weird input. -export function numberChannel(source: ValueArray) { +export function numberChannel(source: any) { return source == null ? null : {