legendCategory.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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 { Category } from '@antv/gui';
  13. import { last } from '@antv/util';
  14. import { format } from 'd3-format';
  15. import { useMarker } from '../utils/marker';
  16. import { adaptor, domainOf, LegendCategoryLayout, inferComponentLayout, inferComponentShape, scaleOf, titleContent, } from './utils';
  17. function inferLayout(position, gridRow, gridCol) {
  18. const [gridRowLimit, gridColLimit] = [gridRow || 100, gridCol || 100];
  19. switch (position) {
  20. case 'top':
  21. case 'bottom':
  22. case 'top-left':
  23. case 'top-right':
  24. case 'bottom-left':
  25. case 'bottom-right':
  26. return [1, gridColLimit];
  27. case 'left':
  28. case 'right':
  29. return [gridRowLimit, 1];
  30. default:
  31. return [gridRow, gridCol];
  32. }
  33. }
  34. function createShape(shape, library, coordinate, theme, style = {}) {
  35. var _a, _b;
  36. const marker = ((_b = (_a = library[`shape.${shape}`]) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.defaultMarker) ||
  37. last(shape.split('.'));
  38. return () => useMarker(marker, style)(0, 0, 6);
  39. }
  40. function inferShape(scales, markState) {
  41. const shapeScale = scaleOf(scales, 'shape');
  42. const colorScale = scaleOf(scales, 'color');
  43. // infer the main shape if multiple marks are used
  44. const shapes = [];
  45. for (const [mark, state] of markState) {
  46. const namespace = mark.type;
  47. const domain = (colorScale === null || colorScale === void 0 ? void 0 : colorScale.getOptions().domain.length) > 0
  48. ? colorScale === null || colorScale === void 0 ? void 0 : colorScale.getOptions().domain
  49. : state.data;
  50. const shape = domain.map((d, i) => {
  51. var _a;
  52. if (shapeScale)
  53. return shapeScale.map(d || 'point');
  54. return ((_a = mark === null || mark === void 0 ? void 0 : mark.style) === null || _a === void 0 ? void 0 : _a.shape) || state.defaultShape || 'point';
  55. });
  56. if (typeof namespace === 'string')
  57. shapes.push([namespace, shape]);
  58. }
  59. if (shapes.length === 0)
  60. return ['point', ['point']];
  61. if (shapes.length === 1)
  62. return shapes[0];
  63. if (!shapeScale)
  64. return shapes[0];
  65. // Evaluate the maximum likelihood of shape
  66. const { range } = shapeScale.getOptions();
  67. return shapes
  68. .map(([namespace, shape]) => {
  69. let sum = 0;
  70. for (let i = 0; i < shapes.length; i++) {
  71. const targetShape = range[i % range.length];
  72. if (shape[i] === targetShape)
  73. sum++;
  74. }
  75. return [sum / shape.length, [namespace, shape]];
  76. })
  77. .sort((a, b) => b[0] - a[0])[0][1];
  78. }
  79. function inferItemMarker(options, { scales, library, coordinate, theme, markState }) {
  80. const shapeScale = scaleOf(scales, 'shape');
  81. const [namespace, shapes] = inferShape(scales, markState);
  82. const { itemMarker } = options;
  83. if (shapeScale && !itemMarker) {
  84. return (d, i) => createShape(`${namespace}.${shapes[i]}`, library, coordinate, theme, {
  85. color: d.color,
  86. });
  87. }
  88. if (typeof itemMarker === 'function')
  89. return itemMarker;
  90. return (d, i) => createShape(itemMarker || `${namespace}.${shapes[i]}`, library, coordinate, theme, {
  91. color: d.color,
  92. });
  93. }
  94. function inferItemMarkerOpacity(scales) {
  95. const scale = scaleOf(scales, 'opacity');
  96. if (scale) {
  97. const { range } = scale.getOptions();
  98. return (d, i) => range[i];
  99. }
  100. return undefined;
  101. }
  102. function inferItemMarkerSize(scales) {
  103. const scale = scaleOf(scales, 'size');
  104. // only support constant size scale.
  105. // size in category legend means the marker radius.
  106. if (scale)
  107. return scale.map(NaN) * 2;
  108. return 8;
  109. }
  110. function inferCategoryStyle(options, context) {
  111. const { labelFormatter = (d) => `${d}` } = options;
  112. const { scales } = context;
  113. const baseStyle = {
  114. itemMarker: inferItemMarker(options, context),
  115. itemMarkerSize: inferItemMarkerSize(scales),
  116. itemMarkerOpacity: inferItemMarkerOpacity(scales),
  117. };
  118. const finalLabelFormatter = typeof labelFormatter === 'string'
  119. ? format(labelFormatter)
  120. : labelFormatter;
  121. // here must exists a color scale
  122. const colorScale = scaleOf(scales, 'color');
  123. const domain = domainOf(scales);
  124. return Object.assign(Object.assign({}, baseStyle), { data: domain.map((d) => ({
  125. id: d,
  126. label: finalLabelFormatter(d),
  127. color: colorScale.map(d),
  128. })) });
  129. }
  130. function inferLegendShape(value, options, component) {
  131. const { position } = options;
  132. if (position === 'center') {
  133. const { bbox } = value;
  134. // to be comfirm: if position is center, we should use the width and height of user definition.
  135. const { width, height } = bbox;
  136. return { width, height };
  137. }
  138. const { width, height } = inferComponentShape(value, options, component);
  139. return { width, height };
  140. }
  141. /**
  142. * Guide Component for ordinal color scale.
  143. */
  144. export const LegendCategory = (options) => {
  145. const { labelFormatter, layout, order, orientation, position, size, title } = options, style = __rest(options, ["labelFormatter", "layout", "order", "orientation", "position", "size", "title"]);
  146. const { gridCol, gridRow } = style;
  147. return (context) => {
  148. var _a, _b, _c;
  149. const { value, theme } = context;
  150. const { bbox } = value;
  151. const { width, height } = inferLegendShape(value, options, LegendCategory);
  152. const finalLayout = inferComponentLayout(position, (_c = (_b = (_a = value.scales) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.guide) === null || _c === void 0 ? void 0 : _c.layout);
  153. const [finalGridRow, finalGridCol] = inferLayout(position, gridRow, gridCol);
  154. const legendStyle = Object.assign({ orientation: ['right', 'left', 'center'].includes(position)
  155. ? 'vertical'
  156. : 'horizontal', width,
  157. height, gridCol: gridCol !== null && gridCol !== void 0 ? gridCol : finalGridCol, gridRow: gridRow !== null && gridRow !== void 0 ? gridRow : finalGridRow, rowPadding: 0, colPadding: 8, titleText: titleContent(title) }, inferCategoryStyle(options, context));
  158. const { legend: legendTheme = {} } = theme;
  159. const categoryStyle = adaptor(Object.assign({}, legendTheme, legendStyle, style));
  160. const layoutWrapper = new LegendCategoryLayout({
  161. style: Object.assign(Object.assign({ x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height }, finalLayout), {
  162. // @ts-ignore
  163. subOptions: categoryStyle }),
  164. });
  165. layoutWrapper.appendChild(new Category({
  166. className: 'legend-category',
  167. style: categoryStyle,
  168. }));
  169. return layoutWrapper;
  170. };
  171. };
  172. LegendCategory.props = {
  173. defaultPosition: 'top',
  174. defaultOrder: 1,
  175. defaultSize: 40,
  176. };
  177. //# sourceMappingURL=legendCategory.js.map