axis.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. "use strict";
  2. var __rest = (this && this.__rest) || function (s, e) {
  3. var t = {};
  4. for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
  5. t[p] = s[p];
  6. if (s != null && typeof Object.getOwnPropertySymbols === "function")
  7. for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
  8. if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
  9. t[p[i]] = s[p[i]];
  10. }
  11. return t;
  12. };
  13. Object.defineProperty(exports, "__esModule", { value: true });
  14. exports.ArcAxis = exports.LinearAxis = void 0;
  15. const gui_1 = require("@antv/gui");
  16. const scale_1 = require("@antv/scale");
  17. const util_1 = require("@antv/util");
  18. const d3_array_1 = require("d3-array");
  19. const d3_format_1 = require("d3-format");
  20. const coordinate_1 = require("../utils/coordinate");
  21. const helper_1 = require("../utils/helper");
  22. const utils_1 = require("./utils");
  23. function sizeOf(coordinate) {
  24. // @ts-ignore
  25. const { innerWidth, innerHeight } = coordinate.getOptions();
  26. return [innerWidth, innerHeight];
  27. }
  28. function reverseTicks(ticks) {
  29. return ticks.map((_a) => {
  30. var { value } = _a, rest = __rest(_a, ["value"]);
  31. return (Object.assign({ value: 1 - value }, rest));
  32. });
  33. }
  34. function createFisheye(position, coordinate) {
  35. const { width, height } = coordinate.getOptions();
  36. return (tick) => {
  37. if (!(0, coordinate_1.isFisheye)(coordinate))
  38. return tick;
  39. const tickPoint = position === 'bottom' ? [tick, 1] : [0, tick];
  40. const vector = coordinate.map(tickPoint);
  41. if (position === 'bottom') {
  42. const v = vector[0];
  43. const x = new scale_1.Linear({
  44. domain: [0, width],
  45. range: [0, 1],
  46. });
  47. return x.map(v);
  48. }
  49. else if (position === 'left') {
  50. const v = vector[1];
  51. const x = new scale_1.Linear({
  52. domain: [0, height],
  53. range: [0, 1],
  54. });
  55. return x.map(v);
  56. }
  57. return tick;
  58. };
  59. }
  60. function ticksOf(scale, domain, tickMethod) {
  61. if (scale.getTicks)
  62. return scale.getTicks();
  63. if (!tickMethod)
  64. return domain;
  65. const [min, max] = (0, d3_array_1.extent)(domain, (d) => +d);
  66. const { tickCount } = scale.getOptions();
  67. return tickMethod(min, max, tickCount);
  68. }
  69. function prettyNumber(n) {
  70. if (typeof n !== 'number')
  71. return n;
  72. return Math.abs(n) < 1e-15 ? n : parseFloat(n.toFixed(15));
  73. }
  74. // Set inset for axis.
  75. function createInset(position, coordinate) {
  76. const options = coordinate.getOptions();
  77. const { innerWidth, innerHeight, insetTop, insetBottom, insetLeft, insetRight, } = options;
  78. const [start, end, size] = position === 'left' || position === 'right'
  79. ? [insetTop, insetBottom, innerHeight]
  80. : [insetLeft, insetRight, innerWidth];
  81. const x = new scale_1.Linear({
  82. domain: [0, 1],
  83. range: [start / size, 1 - end / size],
  84. });
  85. return (i) => x.map(i);
  86. }
  87. /**
  88. * Calc ticks based on scale and coordinate.
  89. */
  90. function getData(scale, domain, tickCount, defaultTickFormatter, tickFilter, tickMethod, position, coordinate) {
  91. var _a;
  92. if (tickCount !== undefined || tickMethod !== undefined) {
  93. scale.update(Object.assign(Object.assign({}, (tickCount && { tickCount })), (tickMethod && { tickMethod })));
  94. }
  95. const ticks = ticksOf(scale, domain, tickMethod);
  96. const filteredTicks = tickFilter ? ticks.filter(tickFilter) : ticks;
  97. const toString = (d) => d instanceof Date
  98. ? String(d)
  99. : typeof d === 'object' && !!d
  100. ? d
  101. : String(d);
  102. const labelFormatter = defaultTickFormatter || ((_a = scale.getFormatter) === null || _a === void 0 ? void 0 : _a.call(scale)) || toString;
  103. const applyInset = createInset(position, coordinate);
  104. const applyFisheye = createFisheye(position, coordinate);
  105. const isHorizontal = (position) => ['top', 'bottom', 'center', 'outer'].includes(position);
  106. // @todo GUI should consider the overlap problem for the first
  107. // and label of arc axis.
  108. if ((0, coordinate_1.isPolar)(coordinate) || (0, coordinate_1.isTranspose)(coordinate)) {
  109. return filteredTicks.map((d, i, array) => {
  110. var _a, _b;
  111. const offset = ((_a = scale.getBandWidth) === null || _a === void 0 ? void 0 : _a.call(scale, d)) / 2 || 0;
  112. const tick = applyInset(scale.map(d) + offset);
  113. const shouldReverse = ((0, coordinate_1.isRadial)(coordinate) && position === 'center') ||
  114. ((0, coordinate_1.isTranspose)(coordinate) &&
  115. ((_b = scale.getTicks) === null || _b === void 0 ? void 0 : _b.call(scale)) &&
  116. isHorizontal(position));
  117. return {
  118. value: shouldReverse ? 1 - tick : tick,
  119. label: toString(labelFormatter(prettyNumber(d), i, array)),
  120. id: String(i),
  121. };
  122. });
  123. }
  124. return filteredTicks.map((d, i, array) => {
  125. var _a;
  126. const offset = ((_a = scale.getBandWidth) === null || _a === void 0 ? void 0 : _a.call(scale, d)) / 2 || 0;
  127. const tick = applyFisheye(applyInset(scale.map(d) + offset));
  128. return {
  129. value: tick,
  130. label: toString(labelFormatter(prettyNumber(d), i, array)),
  131. id: String(i),
  132. };
  133. });
  134. }
  135. function inferGridLength(position, coordinate) {
  136. const [width, height] = sizeOf(coordinate);
  137. if (position.includes('bottom') || position.includes('top'))
  138. return height;
  139. return width;
  140. }
  141. function inferLabelOverlap(transform = [], style) {
  142. if (transform.length > 0)
  143. return transform;
  144. const { labelAutoRotate, labelAutoHide, labelAutoEllipsis, labelAutoWrap } = style;
  145. const finalTransforms = [];
  146. const addToTransforms = (overlap, state) => {
  147. if (state) {
  148. finalTransforms.push(overlap);
  149. }
  150. };
  151. addToTransforms({
  152. type: 'rotate',
  153. optionalAngles: [0, 15, 30, 45, 60, 90],
  154. }, labelAutoRotate);
  155. addToTransforms({ type: 'ellipsis', minLength: 20 }, labelAutoEllipsis);
  156. addToTransforms({ type: 'hide' }, labelAutoHide);
  157. addToTransforms({ type: 'wrap', wordWrapWidth: 100, maxLines: 3, recoveryWhenFail: true }, labelAutoWrap);
  158. return finalTransforms;
  159. }
  160. function inferArcStyle(position, bbox, innerRadius, outerRadius, coordinate) {
  161. const { x, y, width, height } = bbox;
  162. const center = [x + width / 2, y + height / 2];
  163. const radius = Math.min(width, height) / 2;
  164. const [startAngle, endAngle] = (0, coordinate_1.angleOf)(coordinate);
  165. const [w, h] = sizeOf(coordinate);
  166. const r = Math.min(w, h) / 2;
  167. const common = {
  168. center,
  169. radius,
  170. startAngle,
  171. endAngle,
  172. titleFillOpacity: 0,
  173. titlePosition: 'inner',
  174. line: false,
  175. tick: true,
  176. gridLength: (outerRadius - innerRadius) * r,
  177. };
  178. if (position === 'inner') {
  179. const [w, h] = sizeOf(coordinate);
  180. const r = Math.min(w, h) / 2;
  181. return Object.assign(Object.assign({}, common), { labelAlign: 'perpendicular', labelDirection: 'positive', labelSpacing: 4, tickDirection: 'positive', gridDirection: 'negative' });
  182. }
  183. // arc outer
  184. return Object.assign(Object.assign({}, common), { labelAlign: 'parallel', labelDirection: 'negative', labelSpacing: 4, tickDirection: 'negative', gridDirection: 'positive' });
  185. }
  186. function inferGrid(value, coordinate, scale) {
  187. if ((0, coordinate_1.isTheta)(coordinate) || (0, coordinate_1.isParallel)(coordinate))
  188. return false;
  189. // Display axis grid for non-discrete values.
  190. return value === undefined ? !!scale.getTicks : value;
  191. }
  192. function inferAxisLinearOverrideStyle(position, orientation, bbox, coordinate) {
  193. const { x, y, width, height } = bbox;
  194. if (position === 'bottom') {
  195. return { startPos: [x, y], endPos: [x + width, y] };
  196. }
  197. if (position === 'left') {
  198. return { startPos: [x + width, y], endPos: [x + width, y + height] };
  199. }
  200. if (position === 'right') {
  201. return { endPos: [x, y + height], startPos: [x, y] };
  202. }
  203. if (position === 'top') {
  204. return { startPos: [x, y + height], endPos: [x + width, y + height] };
  205. }
  206. // linear axis, maybe in parallel, polar, radial or radar systems.
  207. if (position === 'center') {
  208. // axisY
  209. if (orientation === 'vertical') {
  210. return {
  211. startPos: [x + width, y],
  212. endPos: [x + width, y + height],
  213. };
  214. }
  215. // axisX
  216. else if (orientation === 'horizontal') {
  217. return {
  218. startPos: [x, y + height],
  219. endPos: [x + width, y + height],
  220. };
  221. }
  222. // axis with rotate
  223. else if (typeof orientation === 'number') {
  224. const [cx, cy] = coordinate.getCenter();
  225. const [innerRadius, outerRadius] = (0, coordinate_1.radiusOf)(coordinate);
  226. const [startAngle, endAngle] = (0, coordinate_1.angleOf)(coordinate);
  227. const r = Math.min(width, height) / 2;
  228. const innerR = innerRadius * r;
  229. const outerR = outerRadius * r;
  230. const [actualCx, actualCy] = [cx + x, cy + y];
  231. const [cos, sin] = [Math.cos(orientation), Math.sin(orientation)];
  232. const startPos = [
  233. actualCx + outerR * cos,
  234. actualCy + outerR * sin,
  235. ];
  236. const endPos = [
  237. actualCx + innerR * cos,
  238. actualCy + innerR * sin,
  239. ];
  240. return {
  241. startPos,
  242. endPos,
  243. gridClosed: endAngle - startAngle === 360,
  244. gridCenter: [cx + x, y + cy],
  245. gridControlAngles: new Array(3)
  246. .fill(0)
  247. .map((d, i, arr) => ((endAngle - startAngle) / (arr.length - 1)) * i),
  248. };
  249. }
  250. }
  251. // position is inner or outer for arc axis won't be here
  252. return {};
  253. }
  254. const ArcAxisComponent = (options) => {
  255. const { order, size, position, orientation, labelFormatter, tickFilter, tickCount, tickMethod, important = {}, style = {} } = options, rest = __rest(options, ["order", "size", "position", "orientation", "labelFormatter", "tickFilter", "tickCount", "tickMethod", "important", "style"]);
  256. const { title, grid = false } = style;
  257. return ({ scales: [scale], value, coordinate, theme }) => {
  258. const { bbox } = value;
  259. const { domain } = scale.getOptions();
  260. const data = getData(scale, domain, tickCount, labelFormatter, tickFilter, tickMethod, position, coordinate);
  261. const [innerRadius, outerRadius] = (0, coordinate_1.radiusOf)(coordinate);
  262. const defaultStyle = inferArcStyle(position, bbox, innerRadius, outerRadius, coordinate);
  263. const { axis: axisTheme } = theme;
  264. return new gui_1.Axis({
  265. style: (0, utils_1.adaptor)((0, util_1.deepMix)({}, axisTheme, defaultStyle, Object.assign(Object.assign({ type: 'arc', data, titleText: (0, utils_1.titleContent)(title), grid }, rest), important))),
  266. });
  267. };
  268. };
  269. function inferThemeStyle(scale, coordinate, theme, direction, position, orientation) {
  270. const baseStyle = theme.axis;
  271. let furtherStyle = theme.axisLinear;
  272. if (['top', 'right', 'bottom', 'left'].includes(position)) {
  273. furtherStyle = theme[`axis${(0, helper_1.capitalizeFirst)(position)}`];
  274. }
  275. return Object.assign({}, baseStyle, furtherStyle);
  276. }
  277. function inferDefaultStyle(scale, coordinate, theme, direction, position, orientation) {
  278. const themeStyle = inferThemeStyle(scale, coordinate, theme, direction, position, orientation);
  279. if (position === 'center') {
  280. return Object.assign(Object.assign(Object.assign(Object.assign({}, themeStyle), { labelDirection: direction === 'right' ? 'negative' : 'positive' }), (direction === 'center'
  281. ? { labelTransform: 'translate(50%,0)' }
  282. : null)), { tickDirection: direction === 'right' ? 'negative' : 'positive', labelSpacing: direction === 'center' ? 0 : 4, titleSpacing: (0, utils_1.isVertical)(orientation) ? 10 : 0, tick: direction === 'center' ? false : undefined });
  283. }
  284. return themeStyle;
  285. }
  286. const LinearAxisComponent = (options) => {
  287. const { direction = 'left', important = {}, labelFormatter, order, orientation, position, size, style = {}, title, tickCount, tickFilter, tickMethod, transform, indexBBox } = options, userDefinitions = __rest(options, ["direction", "important", "labelFormatter", "order", "orientation", "position", "size", "style", "title", "tickCount", "tickFilter", "tickMethod", "transform", "indexBBox"]);
  288. return ({ scales: [scale], value, coordinate, theme }) => {
  289. const { bbox } = value;
  290. const { domain } = scale.getOptions();
  291. const defaultStyle = inferDefaultStyle(scale, coordinate, theme, direction, position, orientation);
  292. const internalAxisStyle = Object.assign(Object.assign(Object.assign({}, defaultStyle), style), userDefinitions);
  293. const gridLength = inferGridLength(position, coordinate);
  294. const overrideStyle = inferAxisLinearOverrideStyle(position, orientation, bbox, coordinate);
  295. const data = getData(scale, domain, tickCount, labelFormatter, tickFilter, tickMethod, position, coordinate);
  296. // Bind computed bbox if exists.
  297. const labels = indexBBox
  298. ? data.map((d, i) => {
  299. const bbox = indexBBox.get(i);
  300. if (!bbox)
  301. return d;
  302. // bbox: [label, bbox]
  303. // Make than indexBBox can match current label.
  304. if (bbox[0] !== d.label)
  305. return d;
  306. return Object.assign(Object.assign({}, d), { bbox: bbox[1] });
  307. })
  308. : data;
  309. const finalAxisStyle = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, internalAxisStyle), { type: 'linear', data: labels, crossSize: size, titleText: (0, utils_1.titleContent)(title), labelOverlap: inferLabelOverlap(transform, internalAxisStyle), grid: inferGrid(internalAxisStyle.grid, coordinate, scale), gridLength,
  310. // Always showLine, make title could align the end of axis.
  311. line: true, indexBBox }), (!internalAxisStyle.line ? { lineOpacity: 0 } : null)), overrideStyle), important);
  312. // For hide overlap, do not set crossSize.
  313. const hasHide = finalAxisStyle.labelOverlap.find((d) => d.type === 'hide');
  314. if (hasHide)
  315. finalAxisStyle.crossSize = false;
  316. return new gui_1.Axis({
  317. className: 'axis',
  318. style: (0, utils_1.adaptor)(finalAxisStyle),
  319. });
  320. };
  321. };
  322. const axisFactor = (axis) => {
  323. return (options) => {
  324. const { labelFormatter: useDefinedLabelFormatter, labelFilter: userDefinedLabelFilter = () => true, } = options;
  325. return (context) => {
  326. var _a;
  327. const { scales: [scale], } = context;
  328. const ticks = ((_a = scale.getTicks) === null || _a === void 0 ? void 0 : _a.call(scale)) || scale.getOptions().domain;
  329. const labelFormatter = typeof useDefinedLabelFormatter === 'string'
  330. ? (0, d3_format_1.format)(useDefinedLabelFormatter)
  331. : useDefinedLabelFormatter;
  332. const labelFilter = (datum, index, array) => userDefinedLabelFilter(ticks[index], index, ticks);
  333. const normalizedOptions = Object.assign(Object.assign({}, options), { labelFormatter,
  334. labelFilter,
  335. scale });
  336. return axis(normalizedOptions)(context);
  337. };
  338. };
  339. };
  340. exports.LinearAxis = axisFactor(LinearAxisComponent);
  341. exports.ArcAxis = axisFactor(ArcAxisComponent);
  342. exports.LinearAxis.props = {
  343. defaultPosition: 'center',
  344. defaultSize: 45,
  345. defaultOrder: 0,
  346. };
  347. exports.ArcAxis.props = {
  348. defaultPosition: 'outer',
  349. defaultOrientation: 'vertical',
  350. defaultSize: 45,
  351. defaultOrder: 0,
  352. };
  353. //# sourceMappingURL=axis.js.map