CartesianUtils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
  2. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3. function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
  4. function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
  5. function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
  6. function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
  7. function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
  8. function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
  9. function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
  10. import mapValues from 'lodash/mapValues';
  11. import every from 'lodash/every';
  12. import { getTicksOfScale, parseScale, checkDomainOfScale, getBandSizeOfAxis } from './ChartUtils';
  13. import { findChildByType } from './ReactUtils';
  14. import { compareValues, getPercentValue } from './DataUtils';
  15. import { Bar } from '../cartesian/Bar';
  16. /**
  17. * Calculate the scale function, position, width, height of axes
  18. * @param {Object} props Latest props
  19. * @param {Object} axisMap The configuration of axes
  20. * @param {Object} offset The offset of main part in the svg element
  21. * @param {String} axisType The type of axes, x-axis or y-axis
  22. * @param {String} chartName The name of chart
  23. * @return {Object} Configuration
  24. */
  25. export var formatAxisMap = function formatAxisMap(props, axisMap, offset, axisType, chartName) {
  26. var width = props.width,
  27. height = props.height,
  28. layout = props.layout,
  29. children = props.children;
  30. var ids = Object.keys(axisMap);
  31. var steps = {
  32. left: offset.left,
  33. leftMirror: offset.left,
  34. right: width - offset.right,
  35. rightMirror: width - offset.right,
  36. top: offset.top,
  37. topMirror: offset.top,
  38. bottom: height - offset.bottom,
  39. bottomMirror: height - offset.bottom
  40. };
  41. var hasBar = !!findChildByType(children, Bar);
  42. return ids.reduce(function (result, id) {
  43. var axis = axisMap[id];
  44. var orientation = axis.orientation,
  45. domain = axis.domain,
  46. _axis$padding = axis.padding,
  47. padding = _axis$padding === void 0 ? {} : _axis$padding,
  48. mirror = axis.mirror,
  49. reversed = axis.reversed;
  50. var offsetKey = "".concat(orientation).concat(mirror ? 'Mirror' : '');
  51. var calculatedPadding, range, x, y, needSpace;
  52. if (axis.type === 'number' && (axis.padding === 'gap' || axis.padding === 'no-gap')) {
  53. var diff = domain[1] - domain[0];
  54. var smallestDistanceBetweenValues = Infinity;
  55. var sortedValues = axis.categoricalDomain.sort(compareValues);
  56. sortedValues.forEach(function (value, index) {
  57. if (index > 0) {
  58. smallestDistanceBetweenValues = Math.min((value || 0) - (sortedValues[index - 1] || 0), smallestDistanceBetweenValues);
  59. }
  60. });
  61. if (Number.isFinite(smallestDistanceBetweenValues)) {
  62. var smallestDistanceInPercent = smallestDistanceBetweenValues / diff;
  63. var rangeWidth = axis.layout === 'vertical' ? offset.height : offset.width;
  64. if (axis.padding === 'gap') {
  65. calculatedPadding = smallestDistanceInPercent * rangeWidth / 2;
  66. }
  67. if (axis.padding === 'no-gap') {
  68. var gap = getPercentValue(props.barCategoryGap, smallestDistanceInPercent * rangeWidth);
  69. var halfBand = smallestDistanceInPercent * rangeWidth / 2;
  70. calculatedPadding = halfBand - gap - (halfBand - gap) / rangeWidth * gap;
  71. }
  72. }
  73. }
  74. if (axisType === 'xAxis') {
  75. range = [offset.left + (padding.left || 0) + (calculatedPadding || 0), offset.left + offset.width - (padding.right || 0) - (calculatedPadding || 0)];
  76. } else if (axisType === 'yAxis') {
  77. range = layout === 'horizontal' ? [offset.top + offset.height - (padding.bottom || 0), offset.top + (padding.top || 0)] : [offset.top + (padding.top || 0) + (calculatedPadding || 0), offset.top + offset.height - (padding.bottom || 0) - (calculatedPadding || 0)];
  78. } else {
  79. range = axis.range;
  80. }
  81. if (reversed) {
  82. range = [range[1], range[0]];
  83. }
  84. var _parseScale = parseScale(axis, chartName, hasBar),
  85. scale = _parseScale.scale,
  86. realScaleType = _parseScale.realScaleType;
  87. scale.domain(domain).range(range);
  88. checkDomainOfScale(scale);
  89. var ticks = getTicksOfScale(scale, _objectSpread(_objectSpread({}, axis), {}, {
  90. realScaleType: realScaleType
  91. }));
  92. if (axisType === 'xAxis') {
  93. needSpace = orientation === 'top' && !mirror || orientation === 'bottom' && mirror;
  94. x = offset.left;
  95. y = steps[offsetKey] - needSpace * axis.height;
  96. } else if (axisType === 'yAxis') {
  97. needSpace = orientation === 'left' && !mirror || orientation === 'right' && mirror;
  98. x = steps[offsetKey] - needSpace * axis.width;
  99. y = offset.top;
  100. }
  101. var finalAxis = _objectSpread(_objectSpread(_objectSpread({}, axis), ticks), {}, {
  102. realScaleType: realScaleType,
  103. x: x,
  104. y: y,
  105. scale: scale,
  106. width: axisType === 'xAxis' ? offset.width : axis.width,
  107. height: axisType === 'yAxis' ? offset.height : axis.height
  108. });
  109. finalAxis.bandSize = getBandSizeOfAxis(finalAxis, ticks);
  110. if (!axis.hide && axisType === 'xAxis') {
  111. steps[offsetKey] += (needSpace ? -1 : 1) * finalAxis.height;
  112. } else if (!axis.hide) {
  113. steps[offsetKey] += (needSpace ? -1 : 1) * finalAxis.width;
  114. }
  115. return _objectSpread(_objectSpread({}, result), {}, _defineProperty({}, id, finalAxis));
  116. }, {});
  117. };
  118. export var rectWithPoints = function rectWithPoints(_ref, _ref2) {
  119. var x1 = _ref.x,
  120. y1 = _ref.y;
  121. var x2 = _ref2.x,
  122. y2 = _ref2.y;
  123. return {
  124. x: Math.min(x1, x2),
  125. y: Math.min(y1, y2),
  126. width: Math.abs(x2 - x1),
  127. height: Math.abs(y2 - y1)
  128. };
  129. };
  130. /**
  131. * Compute the x, y, width, and height of a box from two reference points.
  132. * @param {Object} coords x1, x2, y1, and y2
  133. * @return {Object} object
  134. */
  135. export var rectWithCoords = function rectWithCoords(_ref3) {
  136. var x1 = _ref3.x1,
  137. y1 = _ref3.y1,
  138. x2 = _ref3.x2,
  139. y2 = _ref3.y2;
  140. return rectWithPoints({
  141. x: x1,
  142. y: y1
  143. }, {
  144. x: x2,
  145. y: y2
  146. });
  147. };
  148. export var ScaleHelper = /*#__PURE__*/function () {
  149. function ScaleHelper(scale) {
  150. _classCallCheck(this, ScaleHelper);
  151. this.scale = scale;
  152. }
  153. return _createClass(ScaleHelper, [{
  154. key: "domain",
  155. get: function get() {
  156. return this.scale.domain;
  157. }
  158. }, {
  159. key: "range",
  160. get: function get() {
  161. return this.scale.range;
  162. }
  163. }, {
  164. key: "rangeMin",
  165. get: function get() {
  166. return this.range()[0];
  167. }
  168. }, {
  169. key: "rangeMax",
  170. get: function get() {
  171. return this.range()[1];
  172. }
  173. }, {
  174. key: "bandwidth",
  175. get: function get() {
  176. return this.scale.bandwidth;
  177. }
  178. }, {
  179. key: "apply",
  180. value: function apply(value) {
  181. var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
  182. bandAware = _ref4.bandAware,
  183. position = _ref4.position;
  184. if (value === undefined) {
  185. return undefined;
  186. }
  187. if (position) {
  188. switch (position) {
  189. case 'start':
  190. {
  191. return this.scale(value);
  192. }
  193. case 'middle':
  194. {
  195. var offset = this.bandwidth ? this.bandwidth() / 2 : 0;
  196. return this.scale(value) + offset;
  197. }
  198. case 'end':
  199. {
  200. var _offset = this.bandwidth ? this.bandwidth() : 0;
  201. return this.scale(value) + _offset;
  202. }
  203. default:
  204. {
  205. return this.scale(value);
  206. }
  207. }
  208. }
  209. if (bandAware) {
  210. var _offset2 = this.bandwidth ? this.bandwidth() / 2 : 0;
  211. return this.scale(value) + _offset2;
  212. }
  213. return this.scale(value);
  214. }
  215. }, {
  216. key: "isInRange",
  217. value: function isInRange(value) {
  218. var range = this.range();
  219. var first = range[0];
  220. var last = range[range.length - 1];
  221. return first <= last ? value >= first && value <= last : value >= last && value <= first;
  222. }
  223. }], [{
  224. key: "create",
  225. value: function create(obj) {
  226. return new ScaleHelper(obj);
  227. }
  228. }]);
  229. }();
  230. _defineProperty(ScaleHelper, "EPS", 1e-4);
  231. export var createLabeledScales = function createLabeledScales(options) {
  232. var scales = Object.keys(options).reduce(function (res, key) {
  233. return _objectSpread(_objectSpread({}, res), {}, _defineProperty({}, key, ScaleHelper.create(options[key])));
  234. }, {});
  235. return _objectSpread(_objectSpread({}, scales), {}, {
  236. apply: function apply(coord) {
  237. var _ref5 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
  238. bandAware = _ref5.bandAware,
  239. position = _ref5.position;
  240. return mapValues(coord, function (value, label) {
  241. return scales[label].apply(value, {
  242. bandAware: bandAware,
  243. position: position
  244. });
  245. });
  246. },
  247. isInRange: function isInRange(coord) {
  248. return every(coord, function (value, label) {
  249. return scales[label].isInRange(value);
  250. });
  251. }
  252. });
  253. };
  254. /** Normalizes the angle so that 0 <= angle < 180.
  255. * @param {number} angle Angle in degrees.
  256. * @return {number} the normalized angle with a value of at least 0 and never greater or equal to 180. */
  257. export function normalizeAngle(angle) {
  258. return (angle % 180 + 180) % 180;
  259. }
  260. /** Calculates the width of the largest horizontal line that fits inside a rectangle that is displayed at an angle.
  261. * @param {Object} size Width and height of the text in a horizontal position.
  262. * @param {number} angle Angle in degrees in which the text is displayed.
  263. * @return {number} The width of the largest horizontal line that fits inside a rectangle that is displayed at an angle.
  264. */
  265. export var getAngledRectangleWidth = function getAngledRectangleWidth(_ref6) {
  266. var width = _ref6.width,
  267. height = _ref6.height;
  268. var angle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  269. // Ensure angle is >= 0 && < 180
  270. var normalizedAngle = normalizeAngle(angle);
  271. var angleRadians = normalizedAngle * Math.PI / 180;
  272. /* Depending on the height and width of the rectangle, we may need to use different formulas to calculate the angled
  273. * width. This threshold defines when each formula should kick in. */
  274. var angleThreshold = Math.atan(height / width);
  275. var angledWidth = angleRadians > angleThreshold && angleRadians < Math.PI - angleThreshold ? height / Math.sin(angleRadians) : width / Math.cos(angleRadians);
  276. return Math.abs(angledWidth);
  277. };