chartIndex.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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.ChartIndex = 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 helper_1 = require("../utils/helper");
  28. const runtime_1 = require("../runtime");
  29. const utils_1 = require("./utils");
  30. function maybeTransform(options) {
  31. const { transform = [] } = options;
  32. const normalizeY = transform.find((d) => d.type === 'normalizeY');
  33. if (normalizeY)
  34. return normalizeY;
  35. const newNormalizeY = { type: 'normalizeY' };
  36. transform.push(newNormalizeY);
  37. options.transform = transform;
  38. return newNormalizeY;
  39. }
  40. function markValue(markState, markName, channels) {
  41. const [value] = Array.from(markState.entries())
  42. .filter(([mark]) => mark.type === markName)
  43. .map(([mark]) => {
  44. const { encode } = mark;
  45. const channel = (name) => {
  46. const channel = encode[name];
  47. return [name, channel ? channel.value : undefined];
  48. };
  49. return Object.fromEntries(channels.map(channel));
  50. });
  51. return value;
  52. }
  53. /**
  54. * @todo Perf
  55. */
  56. function ChartIndex(_a) {
  57. var { wait = 20, leading, trailing = false, labelFormatter = (date) => `${date}` } = _a, style = __rest(_a, ["wait", "leading", "trailing", "labelFormatter"]);
  58. return (context) => {
  59. const { options, view, container, update } = context;
  60. const { markState, scale, coordinate } = view;
  61. // Get line mark value, exit if it is not existed.
  62. const value = markValue(markState, 'line', ['x', 'y', 'series']);
  63. if (!value)
  64. return;
  65. // Prepare channel value.
  66. const { y: Y, x: X, series: S = [] } = value;
  67. const I = Y.map((_, i) => i);
  68. const sortedX = (0, d3_array_1.sort)(I.map((i) => X[i]));
  69. // Clone options and get line mark.
  70. const clonedOptions = (0, util_1.deepMix)({}, options);
  71. const lineMark = clonedOptions.marks.find((d) => d.type === 'line');
  72. // Update domain of y scale for the line mark.
  73. const r = (I) => (0, d3_array_1.max)(I, (i) => +Y[i]) / (0, d3_array_1.min)(I, (i) => +Y[i]);
  74. const k = (0, d3_array_1.max)((0, d3_array_1.rollup)(I, r, (i) => S[i]).values());
  75. const domainY = [1 / k, k];
  76. (0, util_1.deepMix)(lineMark, {
  77. scale: { y: { domain: domainY } },
  78. });
  79. // Prepare shapes.
  80. const plotArea = (0, utils_1.selectPlotArea)(container);
  81. const lines = container.getElementsByClassName(runtime_1.ELEMENT_CLASS_NAME);
  82. const labels = container.getElementsByClassName(runtime_1.LABEL_CLASS_NAME);
  83. // The format of label key: `${elementKey}-index`,
  84. // group labels by elementKey.
  85. const keyofLabel = (d) => d.__data__.key.split('-')[0];
  86. const keyLabels = (0, d3_array_1.group)(labels, keyofLabel);
  87. const rule = new g_1.Line({
  88. style: Object.assign({ x1: 0, y1: 0, x2: 0, y2: plotArea.getAttribute('height'), stroke: 'black', lineWidth: 1 }, (0, helper_1.subObject)(style, 'rule')),
  89. });
  90. const text = new g_1.Text({
  91. style: Object.assign({ x: 0, y: plotArea.getAttribute('height'), text: '', fontSize: 10 }, (0, helper_1.subObject)(style, 'label')),
  92. });
  93. rule.append(text);
  94. plotArea.appendChild(rule);
  95. // Get the closet date to the rule.
  96. const dateByFocus = (coordinate, scaleX, focus) => {
  97. const [normalizedX] = coordinate.invert(focus);
  98. const date = scaleX.invert(normalizedX);
  99. return sortedX[(0, d3_array_1.bisectCenter)(sortedX, date)];
  100. };
  101. // Update rule and label content.
  102. const updateRule = (focus, date) => {
  103. rule.setAttribute('x1', focus[0]);
  104. rule.setAttribute('x2', focus[0]);
  105. text.setAttribute('text', labelFormatter(date));
  106. };
  107. // Store the new inner state alter rerender the view.
  108. let newView;
  109. // Rerender the view to update basis for each line.
  110. const updateBasisByRerender = (focus) => __awaiter(this, void 0, void 0, function* () {
  111. // Find the closetDate to the rule.
  112. const { x: scaleX } = scale;
  113. const date = dateByFocus(coordinate, scaleX, focus);
  114. updateRule(focus, date);
  115. // Update normalize options.
  116. const normalizeY = maybeTransform(lineMark);
  117. normalizeY.groupBy = 'color';
  118. normalizeY.basis = (I, Y) => {
  119. const i = I[(0, d3_array_1.bisector)((i) => X[+i]).center(I, date)];
  120. return Y[i];
  121. };
  122. // Disable animation.
  123. for (const mark of clonedOptions.marks)
  124. mark.animate = false;
  125. const newState = yield update(clonedOptions);
  126. newView = newState.view;
  127. });
  128. // Only apply translate to update basis for each line.
  129. // If performance is ok, there is no need to use this
  130. // strategy to update basis.
  131. const updateBasisByTranslate = (focus) => {
  132. // Find the closetDate to the rule.
  133. const { scale, coordinate } = newView;
  134. const { x: scaleX, y: scaleY } = scale;
  135. const date = dateByFocus(coordinate, scaleX, focus);
  136. updateRule(focus, date);
  137. // Translate mark and label for better performance.
  138. for (const line of lines) {
  139. // Compute transform in y direction.
  140. const { seriesIndex: SI, key } = line.__data__;
  141. const i = SI[(0, d3_array_1.bisector)((i) => X[+i]).center(SI, date)];
  142. const p0 = [0, scaleY.map(1)]; // basis point
  143. const p1 = [0, scaleY.map(Y[i] / Y[SI[0]])];
  144. const [, y0] = coordinate.map(p0);
  145. const [, y1] = coordinate.map(p1);
  146. const dy = y0 - y1;
  147. line.setAttribute('transform', `translate(0, ${dy})`);
  148. // Update line and related label.
  149. const labels = keyLabels.get(key) || [];
  150. for (const label of labels) {
  151. // @todo Replace with style.transform.
  152. // It now has unexpected behavior.
  153. label.setAttribute('dy', dy);
  154. }
  155. }
  156. };
  157. const updateBasis = (0, util_1.throttle)((event) => {
  158. const focus = (0, utils_1.mousePosition)(plotArea, event);
  159. if (!focus)
  160. return;
  161. updateBasisByTranslate(focus);
  162. }, wait, { leading, trailing });
  163. updateBasisByRerender([0, 0]);
  164. plotArea.addEventListener('pointerenter', updateBasis);
  165. plotArea.addEventListener('pointermove', updateBasis);
  166. plotArea.addEventListener('pointerleave', updateBasis);
  167. return () => {
  168. rule.remove();
  169. plotArea.removeEventListener('pointerenter', updateBasis);
  170. plotArea.removeEventListener('pointermove', updateBasis);
  171. plotArea.removeEventListener('pointerleave', updateBasis);
  172. };
  173. };
  174. }
  175. exports.ChartIndex = ChartIndex;
  176. //# sourceMappingURL=chartIndex.js.map