plot.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  1. "use strict";
  2. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  3. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  4. return new (P || (P = Promise))(function (resolve, reject) {
  5. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  6. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  7. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  8. step((generator = generator.apply(thisArg, _arguments || [])).next());
  9. });
  10. };
  11. var __rest = (this && this.__rest) || function (s, e) {
  12. var t = {};
  13. for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
  14. t[p] = s[p];
  15. if (s != null && typeof Object.getOwnPropertySymbols === "function")
  16. for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
  17. if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
  18. t[p[i]] = s[p[i]];
  19. }
  20. return t;
  21. };
  22. Object.defineProperty(exports, "__esModule", { value: true });
  23. exports.applyStyle = exports.plot = void 0;
  24. const g_1 = require("@antv/g");
  25. const util_1 = require("@antv/util");
  26. const d3_array_1 = require("d3-array");
  27. const d3_format_1 = require("d3-format");
  28. const array_1 = require("../utils/array");
  29. const event_1 = require("../utils/event");
  30. const helper_1 = require("../utils/helper");
  31. const selection_1 = require("../utils/selection");
  32. const component_1 = require("./component");
  33. const constant_1 = require("./constant");
  34. const coordinate_1 = require("./coordinate");
  35. const layout_1 = require("./layout");
  36. const library_1 = require("./library");
  37. const mark_1 = require("./mark");
  38. const scale_1 = require("./scale");
  39. const transform_1 = require("./transform");
  40. function plot(options, selection, library, context) {
  41. var _a, _b;
  42. return __awaiter(this, void 0, void 0, function* () {
  43. const [useComposition] = (0, library_1.useLibrary)('composition', library);
  44. const [useInteraction] = (0, library_1.useLibrary)('interaction', library);
  45. // Some helper functions.
  46. const marks = new Set(Object.keys(library)
  47. .map((d) => { var _a; return (_a = /mark\.(.*)/.exec(d)) === null || _a === void 0 ? void 0 : _a[1]; })
  48. .filter(helper_1.defined));
  49. const typeOf = (node) => {
  50. const { type } = node;
  51. if (typeof type === 'function') {
  52. // @ts-ignore
  53. const { props = {} } = type;
  54. const { composite = true } = props;
  55. if (composite)
  56. return 'mark';
  57. }
  58. return typeof type === 'string' && marks.has(type) ? 'mark' : type;
  59. };
  60. const isMark = (node) => typeOf(node) === 'mark';
  61. const isStandardView = (node) => typeOf(node) === 'standardView';
  62. const transform = (node) => {
  63. if (isStandardView(node))
  64. return [node];
  65. const type = typeOf(node);
  66. const composition = useComposition({ type });
  67. return composition(node);
  68. };
  69. // Some temporary variables help parse the view tree.
  70. const views = [];
  71. const viewNode = new Map();
  72. const nodeState = new Map();
  73. const discovered = [options];
  74. const nodeGenerators = [];
  75. while (discovered.length) {
  76. const node = discovered.shift();
  77. if (isStandardView(node)) {
  78. // Initialize view to get data to be visualized. If the marks
  79. // of the view have already been initialized (facet view),
  80. // initialize the view based on the initialized mark states,
  81. // otherwise initialize it from beginning.
  82. const state = nodeState.get(node);
  83. const [view, children] = state
  84. ? initializeState(state, node, library)
  85. : yield initializeView(node, library);
  86. viewNode.set(view, node);
  87. views.push(view);
  88. // Transform children, they will be transformed into
  89. // standardView if they are mark or view node.
  90. const transformedNodes = children
  91. .flatMap(transform)
  92. .map((d) => (0, coordinate_1.coordinate2Transform)(d, library));
  93. discovered.push(...transformedNodes);
  94. // Only StandardView can be treated as facet and it
  95. // should sync position scales among facets normally.
  96. if (transformedNodes.every(isStandardView)) {
  97. const states = yield Promise.all(transformedNodes.map((d) => initializeMarks(d, library)));
  98. // Note!!!
  99. // This will mutate scales for marks.
  100. (0, scale_1.syncFacetsScales)(states);
  101. for (let i = 0; i < transformedNodes.length; i++) {
  102. const nodeT = transformedNodes[i];
  103. const state = states[i];
  104. nodeState.set(nodeT, state);
  105. }
  106. }
  107. }
  108. else {
  109. // Apply transform to get data in advance for non-mark composition
  110. // node, which makes sure that composition node can preprocess the
  111. // data to produce more nodes based on it.
  112. const n = isMark(node) ? node : yield applyTransform(node, library);
  113. const N = transform(n);
  114. if (Array.isArray(N))
  115. discovered.push(...N);
  116. else if (typeof N === 'function')
  117. nodeGenerators.push(N());
  118. }
  119. }
  120. context.emitter.emit(event_1.ChartEvent.BEFORE_PAINT);
  121. // Plot chart.
  122. const enterContainer = new Map();
  123. const updateContainer = new Map();
  124. const transitions = [];
  125. selection
  126. .selectAll(className(constant_1.VIEW_CLASS_NAME))
  127. .data(views, (d) => d.key)
  128. .join((enter) => enter
  129. .append('g')
  130. .attr('className', constant_1.VIEW_CLASS_NAME)
  131. .attr('id', (view) => view.key)
  132. .call(applyTranslate)
  133. .each(function (view) {
  134. plotView(view, (0, selection_1.select)(this), transitions, library, context);
  135. enterContainer.set(view, this);
  136. }), (update) => update.call(applyTranslate).each(function (view) {
  137. plotView(view, (0, selection_1.select)(this), transitions, library, context);
  138. updateContainer.set(view, this);
  139. }), (exit) => exit
  140. .each(function () {
  141. // Remove existed interactions.
  142. const interactions = this['nameInteraction'].values();
  143. for (const interaction of interactions) {
  144. interaction.destroy();
  145. }
  146. })
  147. .remove());
  148. // Apply interactions.
  149. const viewInstanceof = (viewContainer) => {
  150. return Array.from(viewContainer.entries()).map(([view, container]) => ({
  151. view,
  152. container,
  153. options: viewNode.get(view),
  154. update: createUpdateView((0, selection_1.select)(container), library, context),
  155. }));
  156. };
  157. // Interactions for enter views.
  158. const enterViewInstances = viewInstanceof(enterContainer);
  159. for (const target of enterViewInstances) {
  160. const { options } = target;
  161. // A Map index interaction by interaction name.
  162. const nameInteraction = new Map();
  163. target.container['nameInteraction'] = nameInteraction;
  164. // Apply interactions.
  165. for (const typeOption of inferInteraction(options)) {
  166. const [type, option] = typeOption;
  167. if (option) {
  168. const interaction = useInteraction(Object.assign({ type }, option));
  169. const destroy = interaction(target, enterViewInstances, context.emitter);
  170. nameInteraction.set(type, { destroy });
  171. }
  172. }
  173. }
  174. // Interactions for update views.
  175. const updateViewInstances = viewInstanceof(updateContainer);
  176. for (const target of updateViewInstances) {
  177. const { options, container } = target;
  178. const nameInteraction = container['nameInteraction'];
  179. for (const typeOption of inferInteraction(options)) {
  180. const [type, option] = typeOption;
  181. // Remove interaction for existed views.
  182. const prevInteraction = nameInteraction.get(type);
  183. if (prevInteraction)
  184. (_a = prevInteraction.destroy) === null || _a === void 0 ? void 0 : _a.call(prevInteraction);
  185. // Apply new interaction.
  186. if (option) {
  187. const interaction = useInteraction(Object.assign({ type }, option));
  188. const destroy = interaction(target, updateViewInstances, context.emitter);
  189. nameInteraction.set(type, { destroy });
  190. }
  191. }
  192. }
  193. // Author animations.
  194. const { width, height } = options;
  195. const keyframes = [];
  196. for (const nodeGenerator of nodeGenerators) {
  197. // Delay the rendering of animation keyframe. Different animation
  198. // created by different nodeGenerator will play in the same time.
  199. // eslint-disable-next-line no-async-promise-executor
  200. const keyframe = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
  201. for (const node of nodeGenerator) {
  202. const sizedNode = Object.assign({ width, height }, node);
  203. yield plot(sizedNode, selection, library, context);
  204. }
  205. resolve();
  206. }));
  207. keyframes.push(keyframe);
  208. }
  209. context.views = views;
  210. // Clear and update animation.
  211. (_b = context.animations) === null || _b === void 0 ? void 0 : _b.forEach((animation) => animation === null || animation === void 0 ? void 0 : animation.cancel());
  212. context.animations = transitions;
  213. context.emitter.emit(event_1.ChartEvent.AFTER_PAINT);
  214. // Note!!!
  215. // The returned promise will never resolved if one of nodeGenerator
  216. // never stop to yield node, which may created by a keyframe composition
  217. // with iteration count set to infinite.
  218. const finished = transitions
  219. .filter(helper_1.defined)
  220. .map(cancel)
  221. .map((d) => d.finished);
  222. return Promise.all([...finished, ...keyframes]);
  223. });
  224. }
  225. exports.plot = plot;
  226. function applyTranslate(selection) {
  227. selection.style('transform', (d) => `translate(${d.layout.x}, ${d.layout.y})`);
  228. }
  229. function createUpdateView(selection, library, context) {
  230. return (newOptions) => __awaiter(this, void 0, void 0, function* () {
  231. const transitions = [];
  232. const [newView, newChildren] = yield initializeView(newOptions, library);
  233. plotView(newView, selection, transitions, library, context);
  234. updateTooltip(selection, newOptions, newView, library, context);
  235. for (const child of newChildren) {
  236. plot(child, selection, library, context);
  237. }
  238. return { options: newOptions, view: newView };
  239. });
  240. }
  241. function updateTooltip(selection, options, view, library, context) {
  242. var _a;
  243. const [useInteraction] = (0, library_1.useLibrary)('interaction', library);
  244. // Instances for tooltip.
  245. const container = selection.node();
  246. const nameInteraction = container['nameInteraction'];
  247. const tooltipOptions = inferInteraction(options).find(([d]) => d === 'tooltip');
  248. // Destroy older tooltip.
  249. const tooltip = nameInteraction.get('tooltip');
  250. if (!tooltip)
  251. return;
  252. (_a = tooltip.destroy) === null || _a === void 0 ? void 0 : _a.call(tooltip);
  253. if (!tooltipOptions[1])
  254. return;
  255. // Apply new tooltip interaction.
  256. const applyTooltip = useInteraction(Object.assign({ type: 'tooltip' }, tooltipOptions[1]));
  257. const target = {
  258. options,
  259. view,
  260. container: selection.node(),
  261. update: (options) => Promise.resolve(options),
  262. };
  263. applyTooltip(target, [], context.emitter);
  264. }
  265. function initializeView(options, library) {
  266. return __awaiter(this, void 0, void 0, function* () {
  267. const flattenOptions = yield transformMarks(options, library);
  268. const mergedOptions = bubbleOptions(flattenOptions);
  269. // @todo Remove this.
  270. // !!! NOTE: Mute original view options.
  271. // Update interaction and coordinate for this view.
  272. options.interaction = mergedOptions.interaction;
  273. options.coordinate = mergedOptions.coordinate;
  274. const transformedOptions = (0, coordinate_1.coordinate2Transform)(mergedOptions, library);
  275. const state = yield initializeMarks(transformedOptions, library);
  276. return initializeState(state, transformedOptions, library);
  277. });
  278. }
  279. function bubbleOptions(options) {
  280. const { coordinate: viewCoordinate = {}, interaction: viewInteraction = {}, style: viewStyle = {}, marks } = options, rest = __rest(options, ["coordinate", "interaction", "style", "marks"]);
  281. const markCoordinates = marks.map((d) => d.coordinate || {});
  282. const markInteractions = marks.map((d) => d.interaction || {});
  283. const markViewStyles = marks.map((d) => d.viewStyle || {});
  284. const newCoordinate = [...markCoordinates, viewCoordinate].reduceRight((prev, cur) => (0, util_1.deepMix)(prev, cur), {});
  285. const newInteraction = [viewInteraction, ...markInteractions].reduce((prev, cur) => (0, util_1.deepMix)(prev, cur), {});
  286. const newStyle = [...markViewStyles, viewStyle].reduce((prev, cur) => (0, util_1.deepMix)(prev, cur), {});
  287. return Object.assign(Object.assign({}, rest), { marks, coordinate: newCoordinate, interaction: newInteraction, style: newStyle });
  288. }
  289. function transformMarks(options, library) {
  290. return __awaiter(this, void 0, void 0, function* () {
  291. const [useMark, createMark] = (0, library_1.useLibrary)('mark', library);
  292. const { marks } = options;
  293. const flattenMarks = [];
  294. const discovered = [...marks];
  295. // Pre order traversal.
  296. while (discovered.length) {
  297. const [node] = discovered.splice(0, 1);
  298. // Apply data transform to get data.
  299. const mark = (yield applyTransform(node, library));
  300. const { type = (0, helper_1.error)('G2Mark type is required.'), key } = mark;
  301. const { props = {} } = createMark(type);
  302. const { composite = true } = props;
  303. if (!composite)
  304. flattenMarks.push(mark);
  305. else {
  306. // Convert composite mark to marks.
  307. const marks = yield useMark(mark)(options);
  308. const M = Array.isArray(marks) ? marks : [marks];
  309. discovered.unshift(...M.map((d, i) => (Object.assign(Object.assign({}, d), { key: `${key}-${i}` }))));
  310. }
  311. }
  312. return Object.assign(Object.assign({}, options), { marks: flattenMarks });
  313. });
  314. }
  315. function initializeMarks(options, library) {
  316. return __awaiter(this, void 0, void 0, function* () {
  317. const [useTheme] = (0, library_1.useLibrary)('theme', library);
  318. const [, createMark] = (0, library_1.useLibrary)('mark', library);
  319. const { theme: partialTheme, marks: partialMarks, coordinates = [], } = options;
  320. const theme = useTheme(inferTheme(partialTheme));
  321. const markState = new Map();
  322. // Initialize channels for marks.
  323. for (const markOptions of partialMarks) {
  324. const { type } = markOptions;
  325. const { props = {} } = createMark(type);
  326. const markAndState = yield (0, mark_1.initializeMark)(markOptions, props, library);
  327. if (markAndState) {
  328. const [initializedMark, state] = markAndState;
  329. markState.set(initializedMark, state);
  330. }
  331. }
  332. // Group channels by scale key, each group has scale.
  333. const scaleChannels = (0, d3_array_1.group)(Array.from(markState.values()).flatMap((d) => d.channels), ({ scaleKey }) => scaleKey);
  334. // Infer scale for each channel groups.
  335. for (const channels of scaleChannels.values()) {
  336. // Merge scale options for these channels.
  337. const scaleOptions = channels.reduce((total, { scale }) => (0, util_1.deepMix)(total, scale), {});
  338. // Use the fields of the first channel as the title.
  339. const { values: FV } = channels[0];
  340. const fields = Array.from(new Set(FV.map((d) => d.field).filter(helper_1.defined)));
  341. const options = (0, util_1.deepMix)({
  342. guide: { title: fields.length === 0 ? undefined : fields },
  343. field: fields[0],
  344. }, scaleOptions);
  345. // Use the name of the first channel as the scale name.
  346. const { name } = channels[0];
  347. const values = channels.flatMap(({ values }) => values.map((d) => d.value));
  348. const scale = (0, scale_1.inferScale)(name, values, options, coordinates, theme, library);
  349. channels.forEach((channel) => (channel.scale = scale));
  350. }
  351. return markState;
  352. });
  353. }
  354. function initializeState(markState, options, library) {
  355. const [useMark] = (0, library_1.useLibrary)('mark', library);
  356. const [useTheme] = (0, library_1.useLibrary)('theme', library);
  357. const [useLabelTransform] = (0, library_1.useLibrary)('labelTransform', library);
  358. const { key, frame = false, theme: partialTheme, clip, style = {}, labelTransform = [], } = options;
  359. const theme = useTheme(inferTheme(partialTheme));
  360. // Infer components and compute layout.
  361. const states = Array.from(markState.values());
  362. const scales = Array.from(new Set(states.flatMap((d) => d.channels.map((d) => d.scale))));
  363. const components = (0, component_1.inferComponent)(inferComponentScales(Array.from(scales), states, markState), options, library);
  364. const layout = (0, layout_1.computeLayout)(components, options, theme, library);
  365. const coordinate = (0, coordinate_1.createCoordinate)(layout, options, library);
  366. const framedStyle = frame
  367. ? (0, util_1.deepMix)({ mainLineWidth: 1, mainStroke: '#000' }, style)
  368. : style;
  369. // Place components and mutate their bbox.
  370. (0, layout_1.placeComponents)(components, coordinate, layout);
  371. // Calc data to be rendered for each mark.
  372. // @todo More readable APIs for Container which stays
  373. // the same style with JS standard and lodash APIs.
  374. // @todo More proper way to index scale for different marks.
  375. const scaleInstance = {};
  376. const children = [];
  377. for (const [mark, state] of markState.entries()) {
  378. const {
  379. // scale,
  380. // Callback to create children options based on this mark.
  381. children: createChildren,
  382. // The total count of data (both show and hide)for this facet.
  383. // This is for unit visualization to sync data domain.
  384. dataDomain, modifier, key: markKey, } = mark;
  385. const { index, channels, tooltip } = state;
  386. const scale = Object.fromEntries(channels.map(({ name, scale }) => [name, scale]));
  387. // Transform abstract value to visual value by scales.
  388. const markScaleInstance = (0, array_1.mapObject)(scale, (options) => {
  389. return (0, scale_1.useRelationScale)(options, library);
  390. });
  391. Object.assign(scaleInstance, markScaleInstance);
  392. const value = (0, scale_1.applyScale)(channels, markScaleInstance);
  393. // Calc points and transformation for each data,
  394. // and then transform visual value to visual data.
  395. const calcPoints = useMark(mark);
  396. const [I, P, S] = filterValid(calcPoints(index, markScaleInstance, value, coordinate));
  397. const count = dataDomain || I.length;
  398. const T = modifier ? modifier(P, count, layout) : [];
  399. const titleOf = (i) => { var _a, _b; return (_b = (_a = tooltip.title) === null || _a === void 0 ? void 0 : _a[i]) === null || _b === void 0 ? void 0 : _b.value; };
  400. const itemsOf = (i) => tooltip.items.map((V) => V[i]);
  401. const visualData = I.map((d, i) => {
  402. const datum = Object.assign({ points: P[i], transform: T[i], index: d, markKey, viewKey: key }, (tooltip && {
  403. title: titleOf(d),
  404. items: itemsOf(d),
  405. }));
  406. for (const [k, V] of Object.entries(value)) {
  407. datum[k] = V[d];
  408. if (S)
  409. datum[`series${(0, util_1.upperFirst)(k)}`] = S[i].map((i) => V[i]);
  410. }
  411. if (S)
  412. datum['seriesIndex'] = S[i];
  413. if (S && tooltip) {
  414. datum['seriesItems'] = S[i].map((si) => itemsOf(si));
  415. datum['seriesTitle'] = S[i].map((si) => titleOf(si));
  416. }
  417. return datum;
  418. });
  419. state.data = visualData;
  420. state.index = I;
  421. // Create children options by children callback,
  422. // and then propagate data to each child.
  423. const markChildren = createChildren === null || createChildren === void 0 ? void 0 : createChildren(visualData, markScaleInstance, layout);
  424. children.push(...(markChildren || []));
  425. }
  426. const view = {
  427. layout,
  428. theme,
  429. coordinate,
  430. components,
  431. markState,
  432. key,
  433. clip,
  434. scale: scaleInstance,
  435. style: framedStyle,
  436. labelTransform: composeLabelTransform(labelTransform.map(useLabelTransform)),
  437. };
  438. return [view, children];
  439. }
  440. function plotView(view, selection, transitions, library, context) {
  441. return __awaiter(this, void 0, void 0, function* () {
  442. const { components, theme, layout, markState, coordinate, key, style, clip } = view;
  443. // Render background for the different areas.
  444. const { x, y, width, height } = layout, rest = __rest(layout, ["x", "y", "width", "height"]);
  445. const areaKeys = ['view', 'plot', 'main', 'content'];
  446. const I = areaKeys.map((_, i) => i);
  447. const sizeKeys = ['a', 'margin', 'padding', 'inset'];
  448. const areaStyles = areaKeys.map((d) => (0, helper_1.maybeSubObject)(Object.assign({}, theme, style), d));
  449. const areaSizes = sizeKeys.map((d) => (0, helper_1.subObject)(rest, d));
  450. const styleArea = (selection) => selection
  451. .style('x', (i) => areaLayouts[i].x)
  452. .style('y', (i) => areaLayouts[i].y)
  453. .style('width', (i) => areaLayouts[i].width)
  454. .style('height', (i) => areaLayouts[i].height)
  455. .each(function (i) {
  456. applyStyle((0, selection_1.select)(this), areaStyles[i]);
  457. });
  458. let px = 0;
  459. let py = 0;
  460. let pw = width;
  461. let ph = height;
  462. const areaLayouts = I.map((i) => {
  463. const size = areaSizes[i];
  464. const { left = 0, top = 0, bottom = 0, right = 0 } = size;
  465. px += left;
  466. py += top;
  467. pw -= left + right;
  468. ph -= top + bottom;
  469. return {
  470. x: px,
  471. y: py,
  472. width: pw,
  473. height: ph,
  474. };
  475. });
  476. selection
  477. .selectAll(className(constant_1.AREA_CLASS_NAME))
  478. .data(
  479. // Only render area with defined style.
  480. I.filter((i) => (0, helper_1.defined)(areaStyles[i])), (i) => areaKeys[i])
  481. .join((enter) => enter
  482. .append('rect')
  483. .attr('className', constant_1.AREA_CLASS_NAME)
  484. .style('zIndex', -2)
  485. .call(styleArea), (update) => update.call(styleArea), (exit) => exit.remove());
  486. const animationExtent = computeAnimationExtent(markState);
  487. const componentAnimateOptions = animationExtent
  488. ? { duration: animationExtent[1] }
  489. : false;
  490. // Render components.
  491. // @todo renderComponent return ctor and options.
  492. const componentsTransitions = selection
  493. .selectAll(className(constant_1.COMPONENT_CLASS_NAME))
  494. .data(components, (d, i) => `${d.type}-${i}`)
  495. .join((enter) => enter
  496. .append('g')
  497. .style('zIndex', ({ zIndex }) => zIndex || -1)
  498. .attr('className', constant_1.COMPONENT_CLASS_NAME)
  499. .append((options) => (0, component_1.renderComponent)((0, util_1.deepMix)({ animate: componentAnimateOptions }, options), coordinate, theme, library, markState)), (update) => update.transition(function (options) {
  500. const { preserve = false } = options;
  501. if (preserve)
  502. return;
  503. const newComponent = (0, component_1.renderComponent)((0, util_1.deepMix)({ animate: componentAnimateOptions }, options), coordinate, theme, library, markState);
  504. const { attributes } = newComponent;
  505. const [node] = this.childNodes;
  506. return node.update(attributes);
  507. }))
  508. .transitions();
  509. transitions.push(...componentsTransitions.flat().filter(helper_1.defined));
  510. // Main layer is for showing the main visual representation such as marks. There
  511. // may be multiple main layers for a view, each main layer correspond to one of marks.
  512. // @todo Test DOM structure.
  513. const T = selection
  514. .selectAll(className(constant_1.PLOT_CLASS_NAME))
  515. .data([layout], () => key)
  516. .join((enter) => enter
  517. // Make this layer interactive, such as click and mousemove events.
  518. .append('rect')
  519. .style('zIndex', 0)
  520. .style('fill', 'transparent')
  521. .attr('className', constant_1.PLOT_CLASS_NAME)
  522. .call(updateBBox)
  523. .call(updateLayers, Array.from(markState.keys()))
  524. .call(applyClip, clip), (update) => update
  525. .call(updateLayers, Array.from(markState.keys()))
  526. .call((selection) => {
  527. return animationExtent
  528. ? animateBBox(selection, animationExtent)
  529. : updateBBox(selection);
  530. })
  531. .call(applyClip, clip))
  532. .transitions();
  533. transitions.push(...T.flat());
  534. // Render marks with corresponding data.
  535. for (const [mark, state] of markState.entries()) {
  536. const { data } = state;
  537. const { key, class: cls, type } = mark;
  538. const viewNode = selection.select(`#${key}`);
  539. const shapeFunction = createMarkShapeFunction(mark, state, view, library, context);
  540. const enterFunction = createEnterFunction(mark, state, view, library);
  541. const updateFunction = createUpdateFunction(mark, state, view, library);
  542. const exitFunction = createExitFunction(mark, state, view, library);
  543. const facetElements = selectFacetElements(selection, viewNode, cls, 'element');
  544. const T = viewNode
  545. .selectAll(className(constant_1.ELEMENT_CLASS_NAME))
  546. .selectFacetAll(facetElements)
  547. .data(data, (d) => d.key, (d) => d.groupKey)
  548. .join((enter) => enter
  549. .append(shapeFunction)
  550. // Note!!! Only one className can be set.
  551. // Using attribute as alternative for other classNames.
  552. .attr('className', constant_1.ELEMENT_CLASS_NAME)
  553. .attr('markType', type)
  554. .transition(function (data) {
  555. return enterFunction(data, [this]);
  556. }), (update) => update.call((selection) => {
  557. const parent = selection.parent();
  558. const origin = (0, helper_1.useMemo)((node) => {
  559. const [x, y] = node.getBounds().min;
  560. return [x, y];
  561. });
  562. update.transition(function (data, index) {
  563. maybeFacetElement(this, parent, origin);
  564. const node = shapeFunction(data, index);
  565. const animation = updateFunction(data, [this], [node]);
  566. if (animation === null) {
  567. if (this.nodeName === node.nodeName)
  568. (0, helper_1.copyAttributes)(this, node);
  569. else
  570. this.parentNode.replaceChild(node, this);
  571. }
  572. return animation;
  573. });
  574. }), (exit) => {
  575. return exit
  576. .each(function () {
  577. this.__removed__ = true;
  578. })
  579. .transition(function (data) {
  580. return exitFunction(data, [this]);
  581. })
  582. .remove();
  583. }, (merge) => merge
  584. // Append elements to be merged.
  585. .append(shapeFunction)
  586. .attr('className', constant_1.ELEMENT_CLASS_NAME)
  587. .attr('markType', type)
  588. .transition(function (data) {
  589. // Remove merged elements after animation finishing.
  590. const { __fromElements__: fromElements } = this;
  591. const transition = updateFunction(data, fromElements, [this]);
  592. const exit = new selection_1.Selection(fromElements, null, this.parentNode);
  593. exit.transition(transition).remove();
  594. return transition;
  595. }), (split) => split
  596. .transition(function (data) {
  597. // Append splitted shapes.
  598. const enter = new selection_1.Selection([], this.__toData__, this.parentNode);
  599. const toElements = enter
  600. .append(shapeFunction)
  601. .attr('className', constant_1.ELEMENT_CLASS_NAME)
  602. .attr('markType', type)
  603. .nodes();
  604. return updateFunction(data, [this], toElements);
  605. })
  606. // Remove elements to be splitted after animation finishing.
  607. .remove())
  608. .transitions();
  609. transitions.push(...T.flat());
  610. }
  611. // Plot label for this view.
  612. plotLabel(view, selection, transitions, library);
  613. });
  614. }
  615. /**
  616. * Auto hide labels be specify label layout.
  617. */
  618. function plotLabel(view, selection, transitions, library) {
  619. const [useLabelTransform] = (0, library_1.useLibrary)('labelTransform', library);
  620. const { markState, labelTransform } = view;
  621. const labelLayer = selection.select(className(constant_1.LABEL_LAYER_CLASS_NAME)).node();
  622. // A Map index shapeFunction by label.
  623. const labelShapeFunction = new Map();
  624. // A Map index options by label.
  625. const labelDescriptor = new Map();
  626. // Get all labels for this view.
  627. const labels = Array.from(markState.entries()).flatMap(([mark, state]) => {
  628. const { labels: labelOptions = [], key } = mark;
  629. const shapeFunction = createLabelShapeFunction(mark, state, view, library);
  630. const elements = selection
  631. .select(`#${key}`)
  632. .selectAll(className(constant_1.ELEMENT_CLASS_NAME))
  633. .nodes()
  634. // Only select the valid element.
  635. .filter((n) => !n.__removed__);
  636. return labelOptions.flatMap((labelOption, i) => {
  637. const { transform = [] } = labelOption, options = __rest(labelOption, ["transform"]);
  638. return elements.flatMap((e) => {
  639. const L = getLabels(options, i, e);
  640. L.forEach((l) => {
  641. labelShapeFunction.set(l, shapeFunction);
  642. labelDescriptor.set(l, labelOption);
  643. });
  644. return L;
  645. });
  646. });
  647. });
  648. // Render all labels.
  649. const labelShapes = (0, selection_1.select)(labelLayer)
  650. .selectAll(className(constant_1.LABEL_CLASS_NAME))
  651. .data(labels, (d) => d.key)
  652. .join((enter) => enter
  653. .append((d) => labelShapeFunction.get(d)(d))
  654. .attr('className', constant_1.LABEL_CLASS_NAME), (update) => update.each(function (d) {
  655. // @todo Handle Label with different type.
  656. const shapeFunction = labelShapeFunction.get(d);
  657. const node = shapeFunction(d);
  658. (0, helper_1.copyAttributes)(this, node);
  659. }), (exit) => exit.remove())
  660. .nodes();
  661. // Apply group-level transforms.
  662. const labelGroups = (0, d3_array_1.group)(labelShapes, (d) => labelDescriptor.get(d.__data__));
  663. const { coordinate } = view;
  664. for (const [label, shapes] of labelGroups) {
  665. const { transform = [] } = label;
  666. const transformFunction = composeLabelTransform(transform.map(useLabelTransform));
  667. transformFunction(shapes, coordinate);
  668. }
  669. // Apply view-level transform.
  670. if (labelTransform) {
  671. labelTransform(labelShapes, coordinate);
  672. }
  673. }
  674. function composeLabelTransform(transform) {
  675. return (labels, coordinate) => {
  676. for (const t of transform) {
  677. labels = t(labels, coordinate);
  678. }
  679. return labels;
  680. };
  681. }
  682. function getLabels(label, labelIndex, element) {
  683. const { seriesIndex: SI, seriesKey, points, key, index } = element.__data__;
  684. const bounds = getLocalBounds(element);
  685. if (!SI) {
  686. return [
  687. Object.assign(Object.assign({}, label), { key: `${key}-${labelIndex}`, bounds,
  688. index,
  689. points, dependentElement: element }),
  690. ];
  691. }
  692. const selector = normalizeLabelSelector(label);
  693. const F = SI.filter((_, i) => points[i].every(helper_1.defined)).map((index, i) => (Object.assign(Object.assign({}, label), { key: `${seriesKey[i]}-${labelIndex}`, bounds: [points[i]], index,
  694. points, dependentElement: element })));
  695. return selector ? selector(F) : F;
  696. }
  697. function filterValid([I, P, S]) {
  698. if (S)
  699. return [I, P, S];
  700. const definedIndex = [];
  701. const definedPoints = [];
  702. for (let i = 0; i < I.length; i++) {
  703. const d = I[i];
  704. const p = P[i];
  705. if (p.every(([x, y]) => (0, helper_1.defined)(x) && (0, helper_1.defined)(y))) {
  706. definedIndex.push(d);
  707. definedPoints.push(p);
  708. }
  709. }
  710. return [definedIndex, definedPoints];
  711. }
  712. function normalizeLabelSelector(label) {
  713. const { selector } = label;
  714. if (!selector)
  715. return null;
  716. if (typeof selector === 'function')
  717. return selector;
  718. if (selector === 'first')
  719. return (I) => [I[0]];
  720. if (selector === 'last')
  721. return (I) => [I[I.length - 1]];
  722. throw new Error(`Unknown selector: ${selector}`);
  723. }
  724. /**
  725. * Avoid getting error bounds caused by element animations.
  726. * @todo Remove this temporary handle method, if runtime supports
  727. * correct process: drawElement, do label layout and then do
  728. * transitions together.
  729. */
  730. function getLocalBounds(element) {
  731. const cloneElement = element.cloneNode();
  732. const animations = element.getAnimations();
  733. cloneElement.style.visibility = 'hidden';
  734. animations.forEach((animation) => {
  735. const keyframes = animation.effect.getKeyframes();
  736. cloneElement.attr(keyframes[keyframes.length - 1]);
  737. });
  738. element.parentNode.appendChild(cloneElement);
  739. const bounds = cloneElement.getLocalBounds();
  740. cloneElement.destroy();
  741. const { min, max } = bounds;
  742. return [min, max];
  743. }
  744. function createLabelShapeFunction(mark, state, view, library) {
  745. const [useShape] = (0, library_1.useLibrary)('shape', library);
  746. const { data: abstractData } = mark;
  747. const { data: visualData, defaultLabelShape } = state;
  748. const point2d = visualData.map((d) => d.points);
  749. const { theme, coordinate } = view;
  750. return (options) => {
  751. const { index, points } = options;
  752. const datum = abstractData[index];
  753. const { formatter = (d) => `${d}`, transform, style: abstractStyle } = options, abstractOptions = __rest(options, ["formatter", "transform", "style"]);
  754. const visualOptions = (0, array_1.mapObject)(Object.assign(Object.assign({}, abstractOptions), abstractStyle), (d) => valueOf(d, datum, index, abstractData));
  755. const { shape = defaultLabelShape, text } = visualOptions, style = __rest(visualOptions, ["shape", "text"]);
  756. const f = typeof formatter === 'string' ? (0, d3_format_1.format)(formatter) : formatter;
  757. const value = Object.assign(Object.assign({}, style), { text: f(text, datum, index, abstractData) });
  758. const shapeFunction = useShape(Object.assign({ type: `label.${shape}` }, style));
  759. return shapeFunction(points, value, coordinate, theme, point2d);
  760. };
  761. }
  762. function valueOf(value, datum, i, data) {
  763. if (typeof value === 'function')
  764. return value(datum, i, data);
  765. if (typeof value !== 'string')
  766. return value;
  767. if (datum[value] !== undefined)
  768. return datum[value];
  769. return value;
  770. }
  771. /**
  772. * Compute max duration for this frame.
  773. */
  774. function computeAnimationExtent(markState) {
  775. let maxDuration = -Infinity;
  776. let minDelay = Infinity;
  777. for (const [mark, state] of markState) {
  778. const { animate = {} } = mark;
  779. const { data } = state;
  780. const { enter = {}, update = {}, exit = {} } = animate;
  781. const { type: defaultUpdateType, duration: defaultUpdateDuration = 300, delay: defaultUpdateDelay = 0, } = update;
  782. const { type: defaultEnterType, duration: defaultEnterDuration = 300, delay: defaultEnterDelay = 0, } = enter;
  783. const { type: defaultExitType, duration: defaultExitDuration = 300, delay: defaultExitDelay = 0, } = exit;
  784. for (const d of data) {
  785. const { updateType = defaultUpdateType, updateDuration = defaultUpdateDuration, updateDelay = defaultUpdateDelay, enterType = defaultEnterType, enterDuration = defaultEnterDuration, enterDelay = defaultEnterDelay, exitDuration = defaultExitDuration, exitDelay = defaultExitDelay, exitType = defaultExitType, } = d;
  786. if (updateType === undefined || updateType) {
  787. maxDuration = Math.max(maxDuration, updateDuration + updateDelay);
  788. minDelay = Math.min(minDelay, updateDelay);
  789. }
  790. if (exitType === undefined || exitType) {
  791. maxDuration = Math.max(maxDuration, exitDuration + exitDelay);
  792. minDelay = Math.min(minDelay, exitDelay);
  793. }
  794. if (enterType === undefined || enterType) {
  795. maxDuration = Math.max(maxDuration, enterDuration + enterDelay);
  796. minDelay = Math.min(minDelay, enterDelay);
  797. }
  798. }
  799. }
  800. if (maxDuration === -Infinity)
  801. return null;
  802. return [minDelay, maxDuration - minDelay];
  803. }
  804. function selectFacetElements(selection, current, facetClassName, elementClassName) {
  805. const group = selection.node().parentElement;
  806. return group
  807. .findAll((node) => node.style.facet !== undefined &&
  808. node.style.facet === facetClassName &&
  809. node !== current.node())
  810. .flatMap((node) => node.getElementsByClassName(elementClassName));
  811. }
  812. /**
  813. * Update the parent of element and apply transform to make it
  814. * stay in original position.
  815. */
  816. function maybeFacetElement(element, parent, originOf) {
  817. if (!element.__facet__)
  818. return;
  819. // element -> g#main -> rect#plot
  820. const prePlot = element.parentNode.parentNode;
  821. // g#main -> rect#plot
  822. const newPlot = parent.parentNode;
  823. const [px, py] = originOf(prePlot);
  824. const [x, y] = originOf(newPlot);
  825. const translate = `translate(${px - x}, ${py - y})`;
  826. (0, helper_1.appendTransform)(element, translate);
  827. parent.append(element);
  828. }
  829. function createMarkShapeFunction(mark, state, view, library, context) {
  830. const [useShape] = (0, library_1.useLibrary)('shape', library);
  831. const { data: abstractData } = mark;
  832. const { defaultShape, data } = state;
  833. const point2d = data.map((d) => d.points);
  834. const { theme, coordinate } = view;
  835. const { type: markType, style = {} } = mark;
  836. return (data) => {
  837. const { shape: styleShape = defaultShape } = style;
  838. const { shape = styleShape, points, seriesIndex, index: i } = data, v = __rest(data, ["shape", "points", "seriesIndex", "index"]);
  839. const value = Object.assign(Object.assign({}, v), { shape, mark: markType, defaultShape, index: i });
  840. // Get data-driven style.
  841. // If it is a series shape, such as area and line,
  842. // provides the series of abstract data and indices
  843. // for this shape, otherwise the single datum and
  844. // index.
  845. const abstractDatum = seriesIndex
  846. ? seriesIndex.map((i) => abstractData[i])
  847. : abstractData[i];
  848. const I = seriesIndex ? seriesIndex : i;
  849. const visualStyle = (0, array_1.mapObject)(style, (d) => valueOf(d, abstractDatum, I, abstractData));
  850. const shapeFunction = useShape(Object.assign(Object.assign({}, visualStyle), { type: shapeName(mark, shape) }));
  851. return shapeFunction(points, value, coordinate, theme, point2d, context);
  852. };
  853. }
  854. function createAnimationFunction(type, mark, state, view, library) {
  855. var _a;
  856. const [, createShape] = (0, library_1.useLibrary)('shape', library);
  857. const [useAnimation] = (0, library_1.useLibrary)('animation', library);
  858. const { defaultShape } = state;
  859. const { theme, coordinate } = view;
  860. const upperType = (0, util_1.upperFirst)(type);
  861. const key = `default${upperType}Animation`;
  862. const { [key]: defaultAnimation } = createShape(shapeName(mark, defaultShape)).props;
  863. const { [type]: defaultEffectTiming = {} } = theme;
  864. const animate = ((_a = mark.animate) === null || _a === void 0 ? void 0 : _a[type]) || {};
  865. return (data, from, to) => {
  866. const { [`${type}Type`]: animation, [`${type}Delay`]: delay, [`${type}Duration`]: duration, [`${type}Easing`]: easing, } = data;
  867. const options = Object.assign({ type: animation || defaultAnimation }, animate);
  868. if (!options.type)
  869. return null;
  870. const animateFunction = useAnimation(options);
  871. const value = { delay, duration, easing };
  872. const A = animateFunction(from, to, value, coordinate, defaultEffectTiming);
  873. if (!Array.isArray(A))
  874. return [A];
  875. return A;
  876. };
  877. }
  878. function createEnterFunction(mark, state, view, library) {
  879. return createAnimationFunction('enter', mark, state, view, library);
  880. }
  881. /**
  882. * Animation will not cancel automatically, it should be canceled
  883. * manually. This is very important for performance.
  884. */
  885. function cancel(animation) {
  886. animation.finished.then(() => {
  887. animation.cancel();
  888. });
  889. return animation;
  890. }
  891. function createUpdateFunction(mark, state, view, library) {
  892. return createAnimationFunction('update', mark, state, view, library);
  893. }
  894. function createExitFunction(mark, state, view, library) {
  895. return createAnimationFunction('exit', mark, state, view, library);
  896. }
  897. function inferTheme(theme = {}) {
  898. if (typeof theme === 'string')
  899. return { type: theme };
  900. const { type = 'classic' } = theme, rest = __rest(theme, ["type"]);
  901. return Object.assign(Object.assign({}, rest), { type });
  902. }
  903. /**
  904. * @todo Infer builtin tooltips.
  905. */
  906. function inferInteraction(view) {
  907. const defaults = {
  908. event: true,
  909. tooltip: true,
  910. // @todo Inferred by slider self.
  911. sliderFilter: true,
  912. legendFilter: true,
  913. scrollbarFilter: true,
  914. };
  915. const { interaction = {} } = view;
  916. return Object.entries((0, util_1.deepMix)(defaults, interaction));
  917. }
  918. function applyTransform(node, library) {
  919. return __awaiter(this, void 0, void 0, function* () {
  920. const context = { library };
  921. const { data } = node, rest = __rest(node, ["data"]);
  922. if (data == undefined)
  923. return node;
  924. const [, { data: newData }] = yield (0, transform_1.applyDataTransform)([], { data }, context);
  925. return Object.assign({ data: newData }, rest);
  926. });
  927. }
  928. function updateBBox(selection) {
  929. selection
  930. .style('x', (d) => d.paddingLeft)
  931. .style('y', (d) => d.paddingTop)
  932. .style('width', (d) => d.innerWidth)
  933. .style('height', (d) => d.innerHeight);
  934. }
  935. function animateBBox(selection, extent) {
  936. const [delay, duration] = extent;
  937. selection.transition(function (data) {
  938. const { x, y, width, height } = this.style;
  939. const { paddingLeft, paddingTop, innerWidth, innerHeight } = data;
  940. const keyframes = [
  941. {
  942. x,
  943. y,
  944. width,
  945. height,
  946. },
  947. {
  948. x: paddingLeft,
  949. y: paddingTop,
  950. width: innerWidth,
  951. height: innerHeight,
  952. },
  953. ];
  954. return this.animate(keyframes, { delay, duration, fill: 'both' });
  955. });
  956. }
  957. function shapeName(mark, name) {
  958. const { type } = mark;
  959. if (typeof name === 'string')
  960. return `${type}.${name}`;
  961. return name;
  962. }
  963. /**
  964. * Create and update layer for each mark.
  965. * All the layers created here are treated as main layers.
  966. */
  967. function updateLayers(selection, marks) {
  968. const facet = (d) => (d.class !== undefined ? `${d.class}` : '');
  969. // Skip for empty selection, it can't append nodes.
  970. const nodes = selection.nodes();
  971. if (nodes.length === 0)
  972. return;
  973. selection
  974. .selectAll(className(constant_1.MAIN_LAYER_CLASS_NAME))
  975. .data(marks, (d) => d.key)
  976. .join((enter) => enter
  977. .append('g')
  978. .attr('className', constant_1.MAIN_LAYER_CLASS_NAME)
  979. .attr('id', (d) => d.key)
  980. .style('facet', facet)
  981. .style('fill', 'transparent')
  982. .style('zIndex', (d) => { var _a; return (_a = d.zIndex) !== null && _a !== void 0 ? _a : 0; }), (update) => update
  983. .style('facet', facet)
  984. .style('fill', 'transparent')
  985. .style('zIndex', (d) => { var _a; return (_a = d.zIndex) !== null && _a !== void 0 ? _a : 0; }), (exit) => exit.remove());
  986. const labelLayer = selection.select(className(constant_1.LABEL_LAYER_CLASS_NAME)).node();
  987. if (labelLayer)
  988. return;
  989. selection
  990. .append('g')
  991. .attr('className', constant_1.LABEL_LAYER_CLASS_NAME)
  992. .style('zIndex', 0);
  993. }
  994. function className(...names) {
  995. return names.map((d) => `.${d}`).join('');
  996. }
  997. function applyClip(selection, clip) {
  998. if (!selection.node())
  999. return;
  1000. selection.style('clipPath', (data) => {
  1001. if (!clip)
  1002. return null;
  1003. const { paddingTop: y, paddingLeft: x, innerWidth: width, innerHeight: height, } = data;
  1004. return new g_1.Rect({ style: { x, y, width, height } });
  1005. });
  1006. }
  1007. function inferComponentScales(scales, states, markState) {
  1008. // add shape scale to state.
  1009. var _a;
  1010. // for cell, omit shape scale.
  1011. // @todo support shape scale for cell.
  1012. for (const [key] of markState.entries()) {
  1013. if (key.type === 'cell') {
  1014. return scales.filter((scale) => scale.name !== 'shape');
  1015. }
  1016. }
  1017. // can't infer shape scale if there are multiple states.
  1018. if (states.length !== 1 || scales.some((scale) => scale.name === 'shape')) {
  1019. return scales;
  1020. }
  1021. const { defaultShape: shape } = states[0];
  1022. const acceptMarkTypes = ['point', 'line', 'rect', 'hollow'];
  1023. if (!acceptMarkTypes.includes(shape))
  1024. return scales;
  1025. const shapeMap = {
  1026. point: 'point',
  1027. line: 'hyphen',
  1028. rect: 'square',
  1029. hollow: 'hollow',
  1030. };
  1031. // create shape scale
  1032. const field = ((_a = scales.find((scale) => scale.name === 'color')) === null || _a === void 0 ? void 0 : _a.field) || null;
  1033. const shapeScale = {
  1034. field,
  1035. name: 'shape',
  1036. type: 'constant',
  1037. domain: [],
  1038. range: [shapeMap[shape]],
  1039. };
  1040. return [...scales, shapeScale];
  1041. }
  1042. function applyStyle(selection, style) {
  1043. for (const [key, value] of Object.entries(style)) {
  1044. selection.style(key, value);
  1045. }
  1046. }
  1047. exports.applyStyle = applyStyle;
  1048. //# sourceMappingURL=plot.js.map