scale.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. "use strict";
  2. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  3. if (k2 === undefined) k2 = k;
  4. var desc = Object.getOwnPropertyDescriptor(m, k);
  5. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  6. desc = { enumerable: true, get: function() { return m[k]; } };
  7. }
  8. Object.defineProperty(o, k2, desc);
  9. }) : (function(o, m, k, k2) {
  10. if (k2 === undefined) k2 = k;
  11. o[k2] = m[k];
  12. }));
  13. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  14. Object.defineProperty(o, "default", { enumerable: true, value: v });
  15. }) : function(o, v) {
  16. o["default"] = v;
  17. });
  18. var __importStar = (this && this.__importStar) || function (mod) {
  19. if (mod && mod.__esModule) return mod;
  20. var result = {};
  21. if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
  22. __setModuleDefault(result, mod);
  23. return result;
  24. };
  25. Object.defineProperty(exports, "__esModule", { value: true });
  26. exports.isValidScale = exports.isPosition = exports.syncFacetsScales = exports.useRelationScale = exports.useRelation = exports.applyScale = exports.inferScale = void 0;
  27. const scale_1 = require("@antv/scale");
  28. const d3_array_1 = require("d3-array");
  29. const d3ScaleChromatic = __importStar(require("d3-scale-chromatic"));
  30. const util_1 = require("@antv/util");
  31. const array_1 = require("../utils/array");
  32. const helper_1 = require("../utils/helper");
  33. const coordinate_1 = require("./coordinate");
  34. const library_1 = require("./library");
  35. function inferScale(name, values, options, coordinates, theme, library) {
  36. const { guide = {} } = options;
  37. const type = inferScaleType(name, values, options);
  38. if (typeof type !== 'string')
  39. return options;
  40. const expectedDomain = inferScaleDomain(type, name, values, options);
  41. const actualDomain = maybeRatio(type, expectedDomain, options);
  42. 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,
  43. guide,
  44. name,
  45. type });
  46. }
  47. exports.inferScale = inferScale;
  48. function applyScale(channels, scale) {
  49. const scaledValue = {};
  50. for (const channel of channels) {
  51. const { values, name: scaleName } = channel;
  52. const scaleInstance = scale[scaleName];
  53. for (const value of values) {
  54. const { name, value: V } = value;
  55. scaledValue[name] = V.map((d) => scaleInstance.map(d));
  56. }
  57. }
  58. return scaledValue;
  59. }
  60. exports.applyScale = applyScale;
  61. function useRelation(relations) {
  62. if (!relations || !Array.isArray(relations))
  63. return [helper_1.identity, helper_1.identity];
  64. // Store original map and invert.
  65. let map;
  66. let invert;
  67. const conditionalize = (scale) => {
  68. var _a;
  69. map = scale.map.bind(scale);
  70. invert = (_a = scale.invert) === null || _a === void 0 ? void 0 : _a.bind(scale);
  71. // Distinguish functions[function, output] and value[vale, output] relations.
  72. const funcRelations = relations.filter(([v]) => typeof v === 'function');
  73. const valueRelations = relations.filter(([v]) => typeof v !== 'function');
  74. // Update scale.map
  75. const valueOutput = new Map(valueRelations);
  76. scale.map = (x) => {
  77. for (const [verify, value] of funcRelations) {
  78. if (verify(x))
  79. return value;
  80. }
  81. if (valueOutput.has(x))
  82. return valueOutput.get(x);
  83. return map(x);
  84. };
  85. if (!invert)
  86. return scale;
  87. // Update scale.invert
  88. const outputValue = new Map(valueRelations.map(([a, b]) => [b, a]));
  89. const outputFunc = new Map(funcRelations.map(([a, b]) => [b, a]));
  90. scale.invert = (x) => {
  91. if (outputFunc.has(x))
  92. return x;
  93. if (outputValue.has(x))
  94. return outputValue.get(x);
  95. return invert(x);
  96. };
  97. return scale;
  98. };
  99. const deconditionalize = (scale) => {
  100. if (map !== null)
  101. scale.map = map;
  102. if (invert !== null)
  103. scale.invert = invert;
  104. return scale;
  105. };
  106. return [conditionalize, deconditionalize];
  107. }
  108. exports.useRelation = useRelation;
  109. function useRelationScale(options, library) {
  110. const [useScale] = (0, library_1.useLibrary)('scale', library);
  111. const { relations } = options;
  112. const [conditionalize] = useRelation(relations);
  113. const scale = useScale(options);
  114. return conditionalize(scale);
  115. }
  116. exports.useRelationScale = useRelationScale;
  117. function syncFacetsScales(states) {
  118. const scales = states
  119. .flatMap((d) => Array.from(d.values()))
  120. .flatMap((d) => d.channels.map((d) => d.scale));
  121. syncFacetsScaleByChannel(scales, 'x');
  122. syncFacetsScaleByChannel(scales, 'y');
  123. }
  124. exports.syncFacetsScales = syncFacetsScales;
  125. function syncFacetsScaleByChannel(scales, channel) {
  126. const S = scales.filter(({ name, facet = true }) => facet && name === channel);
  127. const D = S.flatMap((d) => d.domain);
  128. const syncedD = S.every(isQuantitativeScale)
  129. ? (0, d3_array_1.extent)(D)
  130. : S.every(isDiscreteScale)
  131. ? Array.from(new Set(D))
  132. : null;
  133. if (syncedD === null)
  134. return;
  135. for (const scale of S) {
  136. scale.domain = syncedD;
  137. }
  138. }
  139. function maybeRatio(type, domain, options) {
  140. const { ratio } = options;
  141. if (ratio === undefined || ratio === null)
  142. return domain;
  143. if (isQuantitativeScale({ type })) {
  144. return clampQuantitativeScale(domain, ratio, type);
  145. }
  146. if (isDiscreteScale({ type }))
  147. return clampDiscreteScale(domain, ratio);
  148. return domain;
  149. }
  150. function clampQuantitativeScale(domain, ratio, type) {
  151. const D = domain.map(Number);
  152. const scale = new scale_1.Linear({
  153. domain: D,
  154. range: [D[0], D[0] + (D[D.length - 1] - D[0]) * ratio],
  155. });
  156. if (type === 'time')
  157. return domain.map((d) => new Date(scale.map(d)));
  158. return domain.map((d) => scale.map(d));
  159. }
  160. function clampDiscreteScale(domain, ratio) {
  161. const index = Math.round(domain.length * ratio);
  162. return domain.slice(0, index);
  163. }
  164. function isQuantitativeScale(scale) {
  165. const { type } = scale;
  166. if (typeof type !== 'string')
  167. return false;
  168. // Do not take quantize, quantile or threshold scale into account,
  169. // because they are not for position scales. If they are, there is
  170. // no need to sync them.
  171. const names = ['linear', 'log', 'pow', 'time'];
  172. return names.includes(type);
  173. }
  174. function isDiscreteScale(scale) {
  175. const { type } = scale;
  176. if (typeof type !== 'string')
  177. return false;
  178. const names = ['band', 'point', 'ordinal'];
  179. return names.includes(type);
  180. }
  181. // @todo More accurate inference for different cases.
  182. function inferScaleType(name, values, options) {
  183. const { type, domain, range } = options;
  184. if (type !== undefined)
  185. return type;
  186. if (isObject(values))
  187. return 'identity';
  188. if (typeof range === 'string')
  189. return 'linear';
  190. if ((domain || range || []).length > 2)
  191. return asOrdinalType(name);
  192. if (domain !== undefined) {
  193. if (isOrdinal([domain]))
  194. return asOrdinalType(name);
  195. if (isTemporal(values))
  196. return 'time';
  197. return asQuantitativeType(name, range);
  198. }
  199. if (isOrdinal(values))
  200. return asOrdinalType(name);
  201. if (isTemporal(values))
  202. return 'time';
  203. return asQuantitativeType(name, range);
  204. }
  205. function inferScaleDomain(type, name, values, options) {
  206. const { domain } = options;
  207. if (domain !== undefined)
  208. return domain;
  209. switch (type) {
  210. case 'linear':
  211. case 'time':
  212. case 'log':
  213. case 'pow':
  214. case 'sqrt':
  215. case 'quantize':
  216. case 'threshold':
  217. return maybeMinMax(inferDomainQ(values, options), options);
  218. case 'band':
  219. case 'ordinal':
  220. case 'point':
  221. return inferDomainC(values);
  222. case 'quantile':
  223. return inferDomainO(values);
  224. case 'sequential':
  225. return maybeMinMax(inferDomainS(values), options);
  226. default:
  227. return [];
  228. }
  229. }
  230. function inferScaleRange(type, name, values, options, domain, theme, library) {
  231. const { range } = options;
  232. if (typeof range === 'string')
  233. return gradientColors(range);
  234. if (range !== undefined)
  235. return range;
  236. const { rangeMin, rangeMax } = options;
  237. switch (type) {
  238. case 'linear':
  239. case 'time':
  240. case 'log':
  241. case 'pow':
  242. case 'sqrt': {
  243. const colors = categoricalColors(values, options, domain, theme, library);
  244. const [r0, r1] = inferRangeQ(name, colors);
  245. return [rangeMin || r0, rangeMax || r1];
  246. }
  247. case 'band':
  248. case 'point':
  249. return [rangeMin || 0, rangeMax || 1];
  250. case 'ordinal': {
  251. return categoricalColors(values, options, domain, theme, library);
  252. }
  253. case 'sequential':
  254. return undefined;
  255. case 'constant':
  256. return [values[0][0]];
  257. default:
  258. return [];
  259. }
  260. }
  261. function inferScaleOptions(type, name, values, options, coordinates) {
  262. switch (type) {
  263. case 'linear':
  264. case 'time':
  265. case 'log':
  266. case 'pow':
  267. case 'sqrt':
  268. return inferOptionsQ(coordinates, options);
  269. case 'band':
  270. case 'point':
  271. return inferOptionsC(type, name, coordinates, options);
  272. case 'sequential':
  273. return inferOptionsS(options);
  274. default:
  275. return options;
  276. }
  277. }
  278. function categoricalColors(values, options, domain, theme, library) {
  279. const [usePalette] = (0, library_1.useLibrary)('palette', library);
  280. const { defaultCategory10: c10, defaultCategory20: c20 } = theme;
  281. const defaultPalette = (0, array_1.unique)(values.flat()).length <= c10.length ? c10 : c20;
  282. const { palette = defaultPalette, offset } = options;
  283. // Built-in palettes have higher priority.
  284. try {
  285. return usePalette({ type: palette });
  286. }
  287. catch (e) {
  288. const colors = interpolatedColors(palette, domain, offset);
  289. if (colors)
  290. return colors;
  291. throw new Error(`Unknown Component: ${palette} `);
  292. }
  293. }
  294. function gradientColors(range) {
  295. return range.split('-');
  296. }
  297. function interpolatedColors(palette, domain, offset = (d) => d) {
  298. if (!palette)
  299. return null;
  300. const fullName = (0, util_1.upperFirst)(palette);
  301. // If scheme have enough colors, then return pre-defined colors.
  302. const scheme = d3ScaleChromatic[`scheme${fullName}`];
  303. const interpolator = d3ScaleChromatic[`interpolate${fullName}`];
  304. if (!scheme && !interpolator)
  305. return null;
  306. if (scheme) {
  307. // If is a one dimension array, return it.
  308. if (!scheme.some(Array.isArray))
  309. return scheme;
  310. const schemeColors = scheme[domain.length];
  311. if (schemeColors)
  312. return schemeColors;
  313. }
  314. // Otherwise interpolate to get full colors.
  315. return domain.map((_, i) => interpolator(offset(i / domain.length)));
  316. }
  317. function inferOptionsS(options) {
  318. const { palette = 'ylGnBu', offset } = options;
  319. const name = (0, util_1.upperFirst)(palette);
  320. const interpolator = d3ScaleChromatic[`interpolate${name}`];
  321. if (!interpolator)
  322. throw new Error(`Unknown palette: ${name}`);
  323. return {
  324. interpolator: offset ? (x) => interpolator(offset(x)) : interpolator,
  325. };
  326. }
  327. function inferOptionsQ(coordinates, options) {
  328. const { interpolate = scale_1.createInterpolateValue, nice = false, tickCount = 5, } = options;
  329. return Object.assign(Object.assign({}, options), { interpolate, nice, tickCount });
  330. }
  331. function inferOptionsC(type, name, coordinates, options) {
  332. if (options.padding !== undefined ||
  333. options.paddingInner !== undefined ||
  334. options.paddingOuter !== undefined) {
  335. return Object.assign(Object.assign({}, options), { unknown: NaN });
  336. }
  337. const padding = inferPadding(type, name, coordinates);
  338. const { paddingInner = padding, paddingOuter = padding } = options;
  339. return Object.assign(Object.assign({}, options), { paddingInner,
  340. paddingOuter,
  341. padding, unknown: NaN });
  342. }
  343. function inferPadding(type, name, coordinates) {
  344. // The scale for enterDelay and enterDuration should has zero padding by default.
  345. // Because there is no need to add extra delay for the start and the end.
  346. if (name === 'enterDelay' || name === 'enterDuration')
  347. return 0;
  348. if (type === 'band') {
  349. return (0, coordinate_1.isTheta)(coordinates) ? 0 : 0.1;
  350. }
  351. // Point scale need 0.5 padding to make interval between first and last point
  352. // equal to other intervals in polar coordinate.
  353. if (type === 'point')
  354. return 0.5;
  355. return 0;
  356. }
  357. function asOrdinalType(name) {
  358. return isQuantitative(name) ? 'point' : 'ordinal';
  359. }
  360. function asQuantitativeType(name, range) {
  361. if (name !== 'color')
  362. return 'linear';
  363. return range ? 'linear' : 'sequential';
  364. }
  365. function maybeMinMax(domain, options) {
  366. if (domain.length === 0)
  367. return domain;
  368. const { domainMin, domainMax } = options;
  369. const [d0, d1] = domain;
  370. return [domainMin !== null && domainMin !== void 0 ? domainMin : d0, domainMax !== null && domainMax !== void 0 ? domainMax : d1];
  371. }
  372. function inferDomainQ(values, options) {
  373. const { zero = false } = options;
  374. let min = Infinity;
  375. let max = -Infinity;
  376. for (const value of values) {
  377. for (const d of value) {
  378. if ((0, helper_1.defined)(d)) {
  379. min = Math.min(min, +d);
  380. max = Math.max(max, +d);
  381. }
  382. }
  383. }
  384. if (min === Infinity)
  385. return [];
  386. return zero ? [Math.min(0, min), max] : [min, max];
  387. }
  388. function inferDomainC(values) {
  389. return Array.from(new Set(values.flat()));
  390. }
  391. function inferDomainO(values) {
  392. return inferDomainC(values).sort();
  393. }
  394. function inferDomainS(values) {
  395. let min = Infinity;
  396. let max = -Infinity;
  397. for (const value of values) {
  398. for (const d of value) {
  399. if ((0, helper_1.defined)(d)) {
  400. min = Math.min(min, +d);
  401. max = Math.max(max, +d);
  402. }
  403. }
  404. }
  405. if (min === Infinity)
  406. return [];
  407. return [min < 0 ? -max : min, max];
  408. }
  409. /**
  410. * @todo More nice default range for enterDelay and enterDuration.
  411. * @todo Move these to channel definition.
  412. */
  413. function inferRangeQ(name, palette) {
  414. if (name === 'enterDelay')
  415. return [0, 1000];
  416. if (name == 'enterDuration')
  417. return [300, 1000];
  418. if (name.startsWith('y') || name.startsWith('position'))
  419. return [1, 0];
  420. if (name === 'color')
  421. return [(0, array_1.firstOf)(palette), (0, array_1.lastOf)(palette)];
  422. if (name === 'opacity')
  423. return [0, 1];
  424. if (name === 'size')
  425. return [1, 10];
  426. return [0, 1];
  427. }
  428. function isOrdinal(values) {
  429. return some(values, (d) => {
  430. const type = typeof d;
  431. return type === 'string' || type === 'boolean';
  432. });
  433. }
  434. function isTemporal(values) {
  435. return some(values, (d) => d instanceof Date);
  436. }
  437. function isObject(values) {
  438. return some(values, helper_1.isStrictObject);
  439. }
  440. function some(values, callback) {
  441. for (const V of values) {
  442. if (V.some(callback))
  443. return true;
  444. }
  445. return false;
  446. }
  447. function isQuantitative(name) {
  448. return (name.startsWith('x') ||
  449. name.startsWith('y') ||
  450. name.startsWith('position') ||
  451. name.startsWith('size'));
  452. }
  453. // Spatial and temporal position.
  454. function isPosition(name) {
  455. return (name.startsWith('x') ||
  456. name.startsWith('y') ||
  457. name.startsWith('position') ||
  458. name === 'enterDelay' ||
  459. name === 'enterDuration' ||
  460. name === 'updateDelay' ||
  461. name === 'updateDuration' ||
  462. name === 'exitDelay' ||
  463. name === 'exitDuration');
  464. }
  465. exports.isPosition = isPosition;
  466. function isValidScale(scale) {
  467. if (!scale || !scale.type)
  468. return false;
  469. if (typeof scale.type === 'function')
  470. return true;
  471. const { type, domain, range, interpolator } = scale;
  472. const isValidDomain = domain && domain.length > 0;
  473. const isValidRange = range && range.length > 0;
  474. if ([
  475. 'linear',
  476. 'sqrt',
  477. 'log',
  478. 'time',
  479. 'pow',
  480. 'threshold',
  481. 'quantize',
  482. 'quantile',
  483. 'ordinal',
  484. 'band',
  485. 'point',
  486. ].includes(type) &&
  487. isValidDomain &&
  488. isValidRange) {
  489. return true;
  490. }
  491. if (['sequential'].includes(type) &&
  492. isValidDomain &&
  493. (isValidRange || interpolator)) {
  494. return true;
  495. }
  496. if (['constant', 'identity'].includes(type) && isValidRange)
  497. return true;
  498. return false;
  499. }
  500. exports.isValidScale = isValidScale;
  501. //# sourceMappingURL=scale.js.map