UNPKG

@uwdata/mosaic-sql

Version:

SQL query construction and analysis.

152 lines (137 loc) 4.29 kB
import type { ExprValue } from '../types.js'; import { LiteralNode } from '../ast/literal.js'; import { ExprNode } from '../ast/node.js'; import { epoch_ms } from '../functions/datetime.js'; import { literal } from '../functions/literal.js'; import { abs, exp, ln, log, sign, sqrt } from '../functions/numeric.js'; import { add, div, mul, pow, sub } from '../functions/operators.js'; import { asNode } from '../util/ast.js'; import { identity } from '../util/identity.js'; import { DateTimeValue } from './util/time-interval.js'; export interface ScaleTransform<T> { apply: (value: T) => number; invert: (value: number) => T; sqlApply: (value: ExprValue) => ExprNode; sqlInvert: (value: ExprNode) => ExprNode; } export type ScaleType = | 'identity' | 'linear' | 'log' | 'symlog' | 'sqrt' | 'pow' | 'time' | 'utc' ; export type ScaleDomain = [number, number] | [Date, Date]; export interface ScaleOptions { /** The scale type, such as `'linear'`, `'log'`, etc. */ type: ScaleType; /** The scale domain, as an array of start and end data values. */ domain?: ScaleDomain; /** * The scale range, as an array of start and end screen pixels. * The range may be omitted for *identity* scales. */ range?: [number, number]; /** The base of the logarithm. For `'log'` scales only. */ base?: number; /** The constant parameter. For `'symlog'` scales only. */ constant?: number; /** The exponent parameter. For `'pow'` scales only. */ exponent?: number; } export type Scale<T> = ScaleTransform<T> & ScaleOptions; function scaleLinear(): ScaleTransform<number> { return { apply: identity, invert: identity, sqlApply: asNode, sqlInvert: identity }; } function scaleLog({ base = null }: {base?: number | null} = { }): ScaleTransform<number> { if (base == null || base === Math.E) { return { apply: Math.log, invert: Math.exp, sqlApply: c => ln(c), sqlInvert: c => exp(c) }; } else if (base === 10) { return { apply: Math.log10, invert: x => Math.pow(10, x), sqlApply: c => log(c), sqlInvert: c => pow(10, c) }; } else { const b = +base; return { apply: x => Math.log(x) / Math.log(b), invert: x => Math.pow(b, x), sqlApply: c => div(ln(c), ln(b)), sqlInvert: c => pow(b, c) }; } } function scaleSymlog({ constant = 1 } = {}): ScaleTransform<number> { const _ = +constant; return { apply: x => Math.sign(x) * Math.log1p(Math.abs(x)), invert: x => Math.sign(x) * Math.exp(Math.abs(x) - _), sqlApply: c => (c = asNode(c), mul(sign(c), ln(add(_, abs(c))))), sqlInvert: c => mul(sign(c), sub(exp(abs(c)), _)) }; } function scaleSqrt(): ScaleTransform<number> { return { apply: x => Math.sign(x) * Math.sqrt(Math.abs(x)), invert: x => Math.sign(x) * x * x, sqlApply: c => (c = asNode(c), mul(sign(c), sqrt(abs(c)))), sqlInvert: c => mul(sign(c), pow(c, 2)) }; } function scalePow({ exponent = 1 } = {}): ScaleTransform<number> { const e = +exponent; return { apply: x => Math.sign(x) * Math.pow(Math.abs(x), e), invert: x => Math.sign(x) * Math.pow(Math.abs(x), 1/e), sqlApply: c => (c = asNode(c), mul(sign(c), pow(abs(c), e))), sqlInvert: c => mul(sign(c), pow(abs(c), div(1, e))) }; } function scaleTime(): ScaleTransform<DateTimeValue> { return { apply: x => Number(x), invert: x => new Date(x), sqlApply: c => c instanceof Date ? literal(+c) : isDateLiteral(c) ? literal(Number(c.value)) : epoch_ms(c), sqlInvert: identity }; } const scales = { identity: scaleLinear, linear: scaleLinear, log: scaleLog, symlog: scaleSymlog, sqrt: scaleSqrt, pow: scalePow, time: scaleTime, utc: scaleTime }; export function scaleTransform<T>(options: ScaleOptions): Scale<T> { const scale = scales[options.type]; if (!scale) throw new Error(`Unrecognized scale type: ${options.type}`); // @ts-expect-error suppress error, revisit later? return { ...options, ...scale(options) }; } /** * Check if a value is a date-valued literal SQL AST node. * @param x The value to test. */ function isDateLiteral(x: unknown): x is LiteralNode { return x instanceof LiteralNode && x.value instanceof Date; }