facetRect.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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 { deepMix } from '@antv/util';
  13. import { extent, group, max } from 'd3-array';
  14. import { calcBBox } from '../utils/vector';
  15. import { Container } from '../utils/container';
  16. import { indexOf } from '../utils/array';
  17. import { useDefaultAdaptor, useOverrideAdaptor } from './utils';
  18. const setScale = useDefaultAdaptor((options) => {
  19. const { encode, data, scale, shareSize = false } = options;
  20. const { x, y } = encode;
  21. const flexDomain = (encode, channel) => {
  22. var _a;
  23. if (encode === undefined || !shareSize)
  24. return {};
  25. const groups = group(data, (d) => d[encode]);
  26. const domain = ((_a = scale === null || scale === void 0 ? void 0 : scale[channel]) === null || _a === void 0 ? void 0 : _a.domain) || Array.from(groups.keys());
  27. const flex = domain.map((key) => {
  28. if (!groups.has(key))
  29. return 1;
  30. return groups.get(key).length;
  31. });
  32. return { domain, flex };
  33. };
  34. return {
  35. scale: {
  36. x: Object.assign(Object.assign({ paddingOuter: 0, paddingInner: 0.1, guide: x === undefined ? null : { position: 'top' } }, (x === undefined && { paddingInner: 0 })), flexDomain(x, 'x')),
  37. y: Object.assign(Object.assign({ range: [0, 1], paddingOuter: 0, paddingInner: 0.1, guide: y === undefined ? null : { position: 'right' } }, (y === undefined && { paddingInner: 0 })), flexDomain(y, 'y')),
  38. },
  39. };
  40. });
  41. /**
  42. * BFS view tree and using the last discovered color encode
  43. * as the top-level encode for this plot. This is useful when
  44. * color encode and color scale is specified in mark node.
  45. * It makes sense because the whole facet should shared the same
  46. * color encoding, but it also can be override with explicity
  47. * encode and scale specification.
  48. */
  49. export const inferColor = useDefaultAdaptor((options) => {
  50. const { data, scale } = options;
  51. const discovered = [options];
  52. let encodeColor;
  53. let scaleColor;
  54. let legendColor;
  55. while (discovered.length) {
  56. const node = discovered.shift();
  57. const { children, encode = {}, scale = {}, legend = {} } = node;
  58. const { color: c } = encode;
  59. const { color: cs } = scale;
  60. const { color: cl } = legend;
  61. if (c !== undefined)
  62. encodeColor = c;
  63. if (cs !== undefined)
  64. scaleColor = cs;
  65. if (cl !== undefined)
  66. legendColor = cl;
  67. if (Array.isArray(children)) {
  68. discovered.push(...children);
  69. }
  70. }
  71. const domainColor = () => {
  72. var _a;
  73. const domain = (_a = scale === null || scale === void 0 ? void 0 : scale.color) === null || _a === void 0 ? void 0 : _a.domain;
  74. if (domain !== undefined)
  75. return [domain];
  76. if (encodeColor === undefined)
  77. return [undefined];
  78. const color = typeof encodeColor === 'function' ? encodeColor : (d) => d[encodeColor];
  79. const values = data.map(color);
  80. if (values.some((d) => typeof d === 'number'))
  81. return [extent(values)];
  82. return [Array.from(new Set(values)), 'ordinal'];
  83. };
  84. const title = typeof encodeColor === 'string' ? encodeColor : '';
  85. const [domain, type] = domainColor();
  86. return {
  87. encode: { color: encodeColor },
  88. scale: { color: deepMix({}, scaleColor, { domain, type }) },
  89. legend: { color: deepMix({ title }, legendColor) },
  90. };
  91. });
  92. export const setAnimation = useDefaultAdaptor(() => ({
  93. animate: {
  94. enterType: 'fadeIn',
  95. },
  96. }));
  97. export const setStyle = useOverrideAdaptor(() => ({
  98. frame: false,
  99. encode: {
  100. shape: 'hollow',
  101. },
  102. style: {
  103. lineWidth: 0,
  104. },
  105. }));
  106. export const toCell = useOverrideAdaptor(() => ({
  107. type: 'cell',
  108. }));
  109. /**
  110. * Do not set cell data directly, the children will get wrong do if do
  111. * so. Use transform to set new data.
  112. **/
  113. export const setData = useOverrideAdaptor((options) => {
  114. const { data } = options;
  115. const connector = {
  116. type: 'custom',
  117. callback: () => {
  118. const { data, encode } = options;
  119. const { x, y } = encode;
  120. const X = x ? Array.from(new Set(data.map((d) => d[x]))) : [];
  121. const Y = y ? Array.from(new Set(data.map((d) => d[y]))) : [];
  122. const cellData = () => {
  123. if (X.length && Y.length) {
  124. const cellData = [];
  125. for (const vx of X) {
  126. for (const vy of Y) {
  127. cellData.push({ [x]: vx, [y]: vy });
  128. }
  129. }
  130. return cellData;
  131. }
  132. if (X.length)
  133. return X.map((d) => ({ [x]: d }));
  134. if (Y.length)
  135. return Y.map((d) => ({ [y]: d }));
  136. };
  137. return cellData();
  138. },
  139. };
  140. return {
  141. data: { type: 'inline', value: data, transform: [connector] },
  142. };
  143. });
  144. /**
  145. * @todo Move some options assignment to runtime.
  146. */
  147. export const setChildren = useOverrideAdaptor((options, subLayout = subLayoutRect, createGuideX = createGuideXRect, createGuideY = createGuideYRect, childOptions = {}) => {
  148. const { data: dataValue, encode, children, scale: facetScale, x: originX = 0, y: originY = 0, shareData = false, key: viewKey, } = options;
  149. const { value: data } = dataValue;
  150. // Only support field encode now.
  151. const { x: encodeX, y: encodeY } = encode;
  152. const { color: facetScaleColor } = facetScale;
  153. const { domain: facetDomainColor } = facetScaleColor;
  154. const createChildren = (visualData, scale, layout) => {
  155. const { x: scaleX, y: scaleY } = scale;
  156. const { paddingLeft, paddingTop } = layout;
  157. const { domain: domainX } = scaleX.getOptions();
  158. const { domain: domainY } = scaleY.getOptions();
  159. const index = indexOf(visualData);
  160. const bboxs = visualData.map(subLayout);
  161. const values = visualData.map(({ x, y }) => [
  162. scaleX.invert(x),
  163. scaleY.invert(y),
  164. ]);
  165. const filters = values.map(([fx, fy]) => (d) => {
  166. const { [encodeX]: x, [encodeY]: y } = d;
  167. const inX = encodeX !== undefined ? x === fx : true;
  168. const inY = encodeY !== undefined ? y === fy : true;
  169. return inX && inY;
  170. });
  171. const facetData2d = filters.map((f) => data.filter(f));
  172. const maxDataDomain = shareData
  173. ? max(facetData2d, (data) => data.length)
  174. : undefined;
  175. const facets = values.map(([fx, fy]) => ({
  176. columnField: encodeX,
  177. columnIndex: domainX.indexOf(fx),
  178. columnValue: fx,
  179. columnValuesLength: domainX.length,
  180. rowField: encodeY,
  181. rowIndex: domainY.indexOf(fy),
  182. rowValue: fy,
  183. rowValuesLength: domainY.length,
  184. }));
  185. const normalizedChildren = facets.map((facet) => {
  186. if (Array.isArray(children))
  187. return children;
  188. return [children(facet)].flat(1);
  189. });
  190. return index.flatMap((i) => {
  191. const [left, top, width, height] = bboxs[i];
  192. const facet = facets[i];
  193. const facetData = facetData2d[i];
  194. const children = normalizedChildren[i];
  195. return children.map((_a) => {
  196. var _b, _c;
  197. var { scale, key, facet: isFacet = true, axis = {}, legend = {} } = _a, rest = __rest(_a, ["scale", "key", "facet", "axis", "legend"]);
  198. const guideY = ((_b = scale === null || scale === void 0 ? void 0 : scale.y) === null || _b === void 0 ? void 0 : _b.guide) || axis.y;
  199. const guideX = ((_c = scale === null || scale === void 0 ? void 0 : scale.x) === null || _c === void 0 ? void 0 : _c.guide) || axis.x;
  200. const defaultScale = {
  201. x: { tickCount: encodeX ? 5 : undefined },
  202. y: { tickCount: encodeY ? 5 : undefined },
  203. };
  204. const newData = isFacet
  205. ? facetData
  206. : facetData.length === 0
  207. ? []
  208. : data;
  209. const newScale = {
  210. color: { domain: facetDomainColor },
  211. };
  212. const newAxis = {
  213. x: createGuide(guideX, createGuideX)(facet, newData),
  214. y: createGuide(guideY, createGuideY)(facet, newData),
  215. };
  216. return Object.assign(Object.assign({ key: `${key}-${i}`, data: newData, x: left + paddingLeft + originX, y: top + paddingTop + originY, parentKey: viewKey, width,
  217. height, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0, frame: newData.length ? true : false, dataDomain: maxDataDomain, scale: deepMix(defaultScale, scale, newScale), axis: deepMix({}, axis, newAxis),
  218. // Hide all legends for child mark by default,
  219. // they are displayed in the top-level.
  220. legend: false }, rest), childOptions);
  221. });
  222. });
  223. };
  224. return {
  225. children: createChildren,
  226. };
  227. });
  228. function subLayoutRect(data) {
  229. const { points } = data;
  230. return calcBBox(points);
  231. }
  232. /**
  233. * Inner guide not show title, tickLine, label and subTickLine,
  234. * if data is empty, do not show guide.
  235. */
  236. export function createInnerGuide(guide, data) {
  237. return data.length
  238. ? deepMix({
  239. title: false,
  240. tick: null,
  241. label: null,
  242. }, guide)
  243. : deepMix({
  244. title: false,
  245. tick: null,
  246. label: null,
  247. grid: null,
  248. }, guide);
  249. }
  250. function createGuideXRect(guide) {
  251. return (facet, data) => {
  252. const { rowIndex, rowValuesLength, columnIndex, columnValuesLength } = facet;
  253. // Only the bottom-most facet show axisX.
  254. if (rowIndex !== rowValuesLength - 1)
  255. return createInnerGuide(guide, data);
  256. // Only the bottom-left facet show title.
  257. const title = columnIndex !== columnValuesLength - 1 ? false : undefined;
  258. // If data is empty, do not show cell.
  259. const grid = data.length ? undefined : null;
  260. return deepMix({ title, grid }, guide);
  261. };
  262. }
  263. function createGuideYRect(guide) {
  264. return (facet, data) => {
  265. const { rowIndex, columnIndex } = facet;
  266. // Only the left-most facet show axisY.
  267. if (columnIndex !== 0)
  268. return createInnerGuide(guide, data);
  269. // Only the left-top facet show title.
  270. const title = rowIndex !== 0 ? false : undefined;
  271. // If data is empty, do not show cell.
  272. const grid = data.length ? undefined : null;
  273. return deepMix({ title, grid }, guide);
  274. };
  275. }
  276. function createGuide(guide, factory) {
  277. if (typeof guide === 'function')
  278. return guide;
  279. if (guide === null)
  280. return () => null;
  281. return factory(guide);
  282. }
  283. export const FacetRect = () => {
  284. return (options) => {
  285. const newOptions = Container.of(options)
  286. .call(toCell)
  287. .call(inferColor)
  288. .call(setAnimation)
  289. .call(setScale)
  290. .call(setStyle)
  291. .call(setData)
  292. .call(setChildren)
  293. .value();
  294. return [newOptions];
  295. };
  296. };
  297. FacetRect.props = {};
  298. //# sourceMappingURL=facetRect.js.map