@antv/g2
Version:
the Grammar of Graphics in Javascript
582 lines • 20.6 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidScale = exports.isPosition = exports.syncFacetsScales = exports.useRelationScale = exports.assignScale = exports.useRelation = exports.collectScales = exports.applyScale = exports.inferScale = void 0;
const scale_1 = require("@antv/scale");
const d3_array_1 = require("d3-array");
const d3ScaleChromatic = __importStar(require("d3-scale-chromatic"));
const util_1 = require("@antv/util");
const array_1 = require("../utils/array");
const helper_1 = require("../utils/helper");
const coordinate_1 = require("./coordinate");
const library_1 = require("./library");
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 });
}
exports.inferScale = inferScale;
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;
}
exports.applyScale = applyScale;
function collectScales(states, options) {
var _a;
const { components = [] } = options;
const NONE_STATIC_KEYS = [
'scale',
'encode',
'axis',
'legend',
'data',
'transform',
];
// From normal marks.
const scales = Array.from(new Set(states.flatMap((d) => d.channels.map((d) => d.scale))));
// From static marks.
const nameScale = new Map(scales.map((scale) => [scale.name, scale]));
for (const component of components) {
const channels = inferChannelsForComponent(component);
for (const channel of channels) {
const scale = nameScale.get(channel);
const staticScale = ((_a = component.scale) === null || _a === void 0 ? void 0 : _a[channel]) || {};
const { independent = false } = staticScale;
if (scale && !independent) {
// Merged with exist scales if is not independent.
const { guide } = scale;
const guide1 = typeof guide === 'boolean' ? {} : guide;
scale.guide = (0, util_1.deepMix)({}, guide1, component);
Object.assign(scale, staticScale);
}
else {
// Append new scales without exit scales or independent.
const options1 = Object.assign(Object.assign({}, staticScale), { expectedDomain: staticScale.domain, name: channel, guide: (0, util_1.omit)(component, NONE_STATIC_KEYS) });
scales.push(options1);
}
}
}
return scales;
}
exports.collectScales = collectScales;
function useRelation(relations) {
if (!relations || !Array.isArray(relations))
return [helper_1.identity, helper_1.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];
}
exports.useRelation = useRelation;
function assignScale(target, source) {
const keys = Object.keys(target);
for (const scale of Object.values(source)) {
const { name } = scale.getOptions();
if (!(name in target))
target[name] = scale;
else {
const I = keys
.filter((d) => d.startsWith(name))
// Reg is for extract `1` from `x1`;
.map((d) => +(d.replace(name, '') || 0));
const index = (0, d3_array_1.max)(I) + 1;
const newKey = `${name}${index}`;
target[newKey] = scale;
scale.getOptions().key = newKey;
}
}
return target;
}
exports.assignScale = assignScale;
function useRelationScale(options, library) {
const [useScale] = (0, library_1.useLibrary)('scale', library);
const { relations } = options;
const [conditionalize] = useRelation(relations);
const scale = useScale(options);
return conditionalize(scale);
}
exports.useRelationScale = useRelationScale;
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');
}
exports.syncFacetsScales = syncFacetsScales;
function inferChannelsForComponent(component) {
const { channels = [], type, scale = {} } = component;
const L = ['shape', 'color', 'opacity', 'size'];
if (channels.length !== 0)
return channels;
if (type === 'axisX')
return ['x'];
if (type === 'axisY')
return ['y'];
if (type === 'legends')
return Object.keys(scale).filter((d) => L.includes(d));
return [];
}
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)
? (0, d3_array_1.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 scale_1.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, quantitative, ordinal } = 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, ordinal);
if (domain !== undefined) {
if (isOrdinal([domain]))
return asOrdinalType(name, ordinal);
if (isTemporal(values))
return 'time';
return asQuantitativeType(name, range, quantitative);
}
if (isOrdinal(values))
return asOrdinalType(name, ordinal);
if (isTemporal(values))
return 'time';
return asQuantitativeType(name, range, quantitative);
}
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 !== null && rangeMin !== void 0 ? rangeMin : r0, rangeMax !== null && rangeMax !== void 0 ? rangeMax : r1];
}
case 'band':
case 'point': {
const min = name === 'size' ? 5 : 0;
const max = name === 'size' ? 10 : 1;
return [rangeMin !== null && rangeMin !== void 0 ? rangeMin : min, rangeMax !== null && rangeMax !== void 0 ? rangeMax : max];
}
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] = (0, library_1.useLibrary)('palette', library);
const { category10: c10, category20: c20 } = theme;
const defaultPalette = (0, array_1.unique)(values.flat()).length <= c10.length ? c10 : c20;
const { palette = defaultPalette, offset } = options;
if (Array.isArray(palette))
return palette;
// 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 = (0, util_1.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 = (0, util_1.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 = scale_1.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 (name === 'size')
return 0;
if (type === 'band')
return (0, coordinate_1.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, defaults) {
if (defaults)
return defaults;
return isQuantitative(name) ? 'point' : 'ordinal';
}
function asQuantitativeType(name, range, defaults) {
if (defaults)
return defaults;
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 ((0, helper_1.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 values.flat().sort();
}
function inferDomainS(values) {
let min = Infinity;
let max = -Infinity;
for (const value of values) {
for (const d of value) {
if ((0, helper_1.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 [(0, array_1.firstOf)(palette), (0, array_1.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, helper_1.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.
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');
}
exports.isPosition = isPosition;
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;
}
exports.isValidScale = isValidScale;
//# sourceMappingURL=scale.js.map
;