axis.js 15 KB

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