| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- import { Linear, createInterpolateValue } from '@antv/scale';
- import { extent } from 'd3-array';
- import * as d3ScaleChromatic from 'd3-scale-chromatic';
- import { upperFirst } from '@antv/util';
- import { firstOf, lastOf, unique } from '../utils/array';
- import { defined, identity, isStrictObject } from '../utils/helper';
- import { isTheta } from './coordinate';
- import { useLibrary } from './library';
- export function inferScale(name, values, options, coordinates, theme, library) {
- const { guide = {} } = options;
- const type = inferScaleType(name, values, options);
- if (typeof type !== 'string')
- return options;
- const expectedDomain = inferScaleDomain(type, name, values, options);
- const actualDomain = maybeRatio(type, expectedDomain, options);
- return Object.assign(Object.assign(Object.assign({}, options), inferScaleOptions(type, name, values, options, coordinates)), { domain: actualDomain, range: inferScaleRange(type, name, values, options, actualDomain, theme, library), expectedDomain,
- guide,
- name,
- type });
- }
- export function applyScale(channels, scale) {
- const scaledValue = {};
- for (const channel of channels) {
- const { values, name: scaleName } = channel;
- const scaleInstance = scale[scaleName];
- for (const value of values) {
- const { name, value: V } = value;
- scaledValue[name] = V.map((d) => scaleInstance.map(d));
- }
- }
- return scaledValue;
- }
- export function useRelation(relations) {
- if (!relations || !Array.isArray(relations))
- return [identity, identity];
- // Store original map and invert.
- let map;
- let invert;
- const conditionalize = (scale) => {
- var _a;
- map = scale.map.bind(scale);
- invert = (_a = scale.invert) === null || _a === void 0 ? void 0 : _a.bind(scale);
- // Distinguish functions[function, output] and value[vale, output] relations.
- const funcRelations = relations.filter(([v]) => typeof v === 'function');
- const valueRelations = relations.filter(([v]) => typeof v !== 'function');
- // Update scale.map
- const valueOutput = new Map(valueRelations);
- scale.map = (x) => {
- for (const [verify, value] of funcRelations) {
- if (verify(x))
- return value;
- }
- if (valueOutput.has(x))
- return valueOutput.get(x);
- return map(x);
- };
- if (!invert)
- return scale;
- // Update scale.invert
- const outputValue = new Map(valueRelations.map(([a, b]) => [b, a]));
- const outputFunc = new Map(funcRelations.map(([a, b]) => [b, a]));
- scale.invert = (x) => {
- if (outputFunc.has(x))
- return x;
- if (outputValue.has(x))
- return outputValue.get(x);
- return invert(x);
- };
- return scale;
- };
- const deconditionalize = (scale) => {
- if (map !== null)
- scale.map = map;
- if (invert !== null)
- scale.invert = invert;
- return scale;
- };
- return [conditionalize, deconditionalize];
- }
- export function useRelationScale(options, library) {
- const [useScale] = useLibrary('scale', library);
- const { relations } = options;
- const [conditionalize] = useRelation(relations);
- const scale = useScale(options);
- return conditionalize(scale);
- }
- export function syncFacetsScales(states) {
- const scales = states
- .flatMap((d) => Array.from(d.values()))
- .flatMap((d) => d.channels.map((d) => d.scale));
- syncFacetsScaleByChannel(scales, 'x');
- syncFacetsScaleByChannel(scales, 'y');
- }
- function syncFacetsScaleByChannel(scales, channel) {
- const S = scales.filter(({ name, facet = true }) => facet && name === channel);
- const D = S.flatMap((d) => d.domain);
- const syncedD = S.every(isQuantitativeScale)
- ? extent(D)
- : S.every(isDiscreteScale)
- ? Array.from(new Set(D))
- : null;
- if (syncedD === null)
- return;
- for (const scale of S) {
- scale.domain = syncedD;
- }
- }
- function maybeRatio(type, domain, options) {
- const { ratio } = options;
- if (ratio === undefined || ratio === null)
- return domain;
- if (isQuantitativeScale({ type })) {
- return clampQuantitativeScale(domain, ratio, type);
- }
- if (isDiscreteScale({ type }))
- return clampDiscreteScale(domain, ratio);
- return domain;
- }
- function clampQuantitativeScale(domain, ratio, type) {
- const D = domain.map(Number);
- const scale = new Linear({
- domain: D,
- range: [D[0], D[0] + (D[D.length - 1] - D[0]) * ratio],
- });
- if (type === 'time')
- return domain.map((d) => new Date(scale.map(d)));
- return domain.map((d) => scale.map(d));
- }
- function clampDiscreteScale(domain, ratio) {
- const index = Math.round(domain.length * ratio);
- return domain.slice(0, index);
- }
- function isQuantitativeScale(scale) {
- const { type } = scale;
- if (typeof type !== 'string')
- return false;
- // Do not take quantize, quantile or threshold scale into account,
- // because they are not for position scales. If they are, there is
- // no need to sync them.
- const names = ['linear', 'log', 'pow', 'time'];
- return names.includes(type);
- }
- function isDiscreteScale(scale) {
- const { type } = scale;
- if (typeof type !== 'string')
- return false;
- const names = ['band', 'point', 'ordinal'];
- return names.includes(type);
- }
- // @todo More accurate inference for different cases.
- function inferScaleType(name, values, options) {
- const { type, domain, range } = options;
- if (type !== undefined)
- return type;
- if (isObject(values))
- return 'identity';
- if (typeof range === 'string')
- return 'linear';
- if ((domain || range || []).length > 2)
- return asOrdinalType(name);
- if (domain !== undefined) {
- if (isOrdinal([domain]))
- return asOrdinalType(name);
- if (isTemporal(values))
- return 'time';
- return asQuantitativeType(name, range);
- }
- if (isOrdinal(values))
- return asOrdinalType(name);
- if (isTemporal(values))
- return 'time';
- return asQuantitativeType(name, range);
- }
- function inferScaleDomain(type, name, values, options) {
- const { domain } = options;
- if (domain !== undefined)
- return domain;
- switch (type) {
- case 'linear':
- case 'time':
- case 'log':
- case 'pow':
- case 'sqrt':
- case 'quantize':
- case 'threshold':
- return maybeMinMax(inferDomainQ(values, options), options);
- case 'band':
- case 'ordinal':
- case 'point':
- return inferDomainC(values);
- case 'quantile':
- return inferDomainO(values);
- case 'sequential':
- return maybeMinMax(inferDomainS(values), options);
- default:
- return [];
- }
- }
- function inferScaleRange(type, name, values, options, domain, theme, library) {
- const { range } = options;
- if (typeof range === 'string')
- return gradientColors(range);
- if (range !== undefined)
- return range;
- const { rangeMin, rangeMax } = options;
- switch (type) {
- case 'linear':
- case 'time':
- case 'log':
- case 'pow':
- case 'sqrt': {
- const colors = categoricalColors(values, options, domain, theme, library);
- const [r0, r1] = inferRangeQ(name, colors);
- return [rangeMin || r0, rangeMax || r1];
- }
- case 'band':
- case 'point':
- return [rangeMin || 0, rangeMax || 1];
- case 'ordinal': {
- return categoricalColors(values, options, domain, theme, library);
- }
- case 'sequential':
- return undefined;
- case 'constant':
- return [values[0][0]];
- default:
- return [];
- }
- }
- function inferScaleOptions(type, name, values, options, coordinates) {
- switch (type) {
- case 'linear':
- case 'time':
- case 'log':
- case 'pow':
- case 'sqrt':
- return inferOptionsQ(coordinates, options);
- case 'band':
- case 'point':
- return inferOptionsC(type, name, coordinates, options);
- case 'sequential':
- return inferOptionsS(options);
- default:
- return options;
- }
- }
- function categoricalColors(values, options, domain, theme, library) {
- const [usePalette] = useLibrary('palette', library);
- const { defaultCategory10: c10, defaultCategory20: c20 } = theme;
- const defaultPalette = unique(values.flat()).length <= c10.length ? c10 : c20;
- const { palette = defaultPalette, offset } = options;
- // Built-in palettes have higher priority.
- try {
- return usePalette({ type: palette });
- }
- catch (e) {
- const colors = interpolatedColors(palette, domain, offset);
- if (colors)
- return colors;
- throw new Error(`Unknown Component: ${palette} `);
- }
- }
- function gradientColors(range) {
- return range.split('-');
- }
- function interpolatedColors(palette, domain, offset = (d) => d) {
- if (!palette)
- return null;
- const fullName = upperFirst(palette);
- // If scheme have enough colors, then return pre-defined colors.
- const scheme = d3ScaleChromatic[`scheme${fullName}`];
- const interpolator = d3ScaleChromatic[`interpolate${fullName}`];
- if (!scheme && !interpolator)
- return null;
- if (scheme) {
- // If is a one dimension array, return it.
- if (!scheme.some(Array.isArray))
- return scheme;
- const schemeColors = scheme[domain.length];
- if (schemeColors)
- return schemeColors;
- }
- // Otherwise interpolate to get full colors.
- return domain.map((_, i) => interpolator(offset(i / domain.length)));
- }
- function inferOptionsS(options) {
- const { palette = 'ylGnBu', offset } = options;
- const name = upperFirst(palette);
- const interpolator = d3ScaleChromatic[`interpolate${name}`];
- if (!interpolator)
- throw new Error(`Unknown palette: ${name}`);
- return {
- interpolator: offset ? (x) => interpolator(offset(x)) : interpolator,
- };
- }
- function inferOptionsQ(coordinates, options) {
- const { interpolate = createInterpolateValue, nice = false, tickCount = 5, } = options;
- return Object.assign(Object.assign({}, options), { interpolate, nice, tickCount });
- }
- function inferOptionsC(type, name, coordinates, options) {
- if (options.padding !== undefined ||
- options.paddingInner !== undefined ||
- options.paddingOuter !== undefined) {
- return Object.assign(Object.assign({}, options), { unknown: NaN });
- }
- const padding = inferPadding(type, name, coordinates);
- const { paddingInner = padding, paddingOuter = padding } = options;
- return Object.assign(Object.assign({}, options), { paddingInner,
- paddingOuter,
- padding, unknown: NaN });
- }
- function inferPadding(type, name, coordinates) {
- // The scale for enterDelay and enterDuration should has zero padding by default.
- // Because there is no need to add extra delay for the start and the end.
- if (name === 'enterDelay' || name === 'enterDuration')
- return 0;
- if (type === 'band') {
- return isTheta(coordinates) ? 0 : 0.1;
- }
- // Point scale need 0.5 padding to make interval between first and last point
- // equal to other intervals in polar coordinate.
- if (type === 'point')
- return 0.5;
- return 0;
- }
- function asOrdinalType(name) {
- return isQuantitative(name) ? 'point' : 'ordinal';
- }
- function asQuantitativeType(name, range) {
- if (name !== 'color')
- return 'linear';
- return range ? 'linear' : 'sequential';
- }
- function maybeMinMax(domain, options) {
- if (domain.length === 0)
- return domain;
- const { domainMin, domainMax } = options;
- const [d0, d1] = domain;
- return [domainMin !== null && domainMin !== void 0 ? domainMin : d0, domainMax !== null && domainMax !== void 0 ? domainMax : d1];
- }
- function inferDomainQ(values, options) {
- const { zero = false } = options;
- let min = Infinity;
- let max = -Infinity;
- for (const value of values) {
- for (const d of value) {
- if (defined(d)) {
- min = Math.min(min, +d);
- max = Math.max(max, +d);
- }
- }
- }
- if (min === Infinity)
- return [];
- return zero ? [Math.min(0, min), max] : [min, max];
- }
- function inferDomainC(values) {
- return Array.from(new Set(values.flat()));
- }
- function inferDomainO(values) {
- return inferDomainC(values).sort();
- }
- function inferDomainS(values) {
- let min = Infinity;
- let max = -Infinity;
- for (const value of values) {
- for (const d of value) {
- if (defined(d)) {
- min = Math.min(min, +d);
- max = Math.max(max, +d);
- }
- }
- }
- if (min === Infinity)
- return [];
- return [min < 0 ? -max : min, max];
- }
- /**
- * @todo More nice default range for enterDelay and enterDuration.
- * @todo Move these to channel definition.
- */
- function inferRangeQ(name, palette) {
- if (name === 'enterDelay')
- return [0, 1000];
- if (name == 'enterDuration')
- return [300, 1000];
- if (name.startsWith('y') || name.startsWith('position'))
- return [1, 0];
- if (name === 'color')
- return [firstOf(palette), lastOf(palette)];
- if (name === 'opacity')
- return [0, 1];
- if (name === 'size')
- return [1, 10];
- return [0, 1];
- }
- function isOrdinal(values) {
- return some(values, (d) => {
- const type = typeof d;
- return type === 'string' || type === 'boolean';
- });
- }
- function isTemporal(values) {
- return some(values, (d) => d instanceof Date);
- }
- function isObject(values) {
- return some(values, isStrictObject);
- }
- function some(values, callback) {
- for (const V of values) {
- if (V.some(callback))
- return true;
- }
- return false;
- }
- function isQuantitative(name) {
- return (name.startsWith('x') ||
- name.startsWith('y') ||
- name.startsWith('position') ||
- name.startsWith('size'));
- }
- // Spatial and temporal position.
- export function isPosition(name) {
- return (name.startsWith('x') ||
- name.startsWith('y') ||
- name.startsWith('position') ||
- name === 'enterDelay' ||
- name === 'enterDuration' ||
- name === 'updateDelay' ||
- name === 'updateDuration' ||
- name === 'exitDelay' ||
- name === 'exitDuration');
- }
- export function isValidScale(scale) {
- if (!scale || !scale.type)
- return false;
- if (typeof scale.type === 'function')
- return true;
- const { type, domain, range, interpolator } = scale;
- const isValidDomain = domain && domain.length > 0;
- const isValidRange = range && range.length > 0;
- if ([
- 'linear',
- 'sqrt',
- 'log',
- 'time',
- 'pow',
- 'threshold',
- 'quantize',
- 'quantile',
- 'ordinal',
- 'band',
- 'point',
- ].includes(type) &&
- isValidDomain &&
- isValidRange) {
- return true;
- }
- if (['sequential'].includes(type) &&
- isValidDomain &&
- (isValidRange || interpolator)) {
- return true;
- }
- if (['constant', 'identity'].includes(type) && isValidRange)
- return true;
- return false;
- }
- //# sourceMappingURL=scale.js.map
|