You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 3 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  2. // Copyright (c) 2016 - 2017 Uber Technologies, Inc.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. import React from 'react';
  22. import PropTypes from 'prop-types';
  23. import { hierarchy, partition } from 'd3-hierarchy';
  24. import { scaleLinear, scaleSqrt } from 'd3-scale';
  25. import { AnimationPropType } from '../animation';
  26. import LabelSeries from '../plot/series/label-series';
  27. import ArcSeries from '../plot/series/arc-series';
  28. import XYPlot from '../plot/xy-plot';
  29. import { getRadialDomain } from '../utils/series-utils';
  30. import { getRadialLayoutMargin } from '../utils/chart-utils';
  31. var predefinedClassName = 'rv-sunburst';
  32. var LISTENERS_TO_OVERWRITE = ['onValueMouseOver', 'onValueMouseOut', 'onValueClick', 'onValueRightClick', 'onSeriesMouseOver', 'onSeriesMouseOut', 'onSeriesClick', 'onSeriesRightClick'];
  33. /**
  34. * Create the list of nodes to render.
  35. * @param {Object} props
  36. props.data {Object} - tree structured data (each node has a name anc an array of children)
  37. props.height {number} - the height of the graphic to be rendered
  38. props.hideRootNode {boolean} - whether or not to hide the root node
  39. props.width {number} - the width of the graphic to be rendered
  40. props.getSize {function} - accessor for the size
  41. * @returns {Array} Array of nodes.
  42. */
  43. function getNodesToRender(_ref) {
  44. var data = _ref.data,
  45. height = _ref.height,
  46. hideRootNode = _ref.hideRootNode,
  47. width = _ref.width,
  48. getSize = _ref.getSize;
  49. var partitionFunction = partition();
  50. var structuredInput = hierarchy(data).sum(getSize);
  51. var radius = Math.min(width, height) / 2 - 10;
  52. var x = scaleLinear().range([0, 2 * Math.PI]);
  53. var y = scaleSqrt().range([0, radius]);
  54. return partitionFunction(structuredInput).descendants().reduce(function (res, cell, index) {
  55. if (hideRootNode && index === 0) {
  56. return res;
  57. }
  58. return res.concat([_extends({
  59. angle0: Math.max(0, Math.min(2 * Math.PI, x(cell.x0))),
  60. angle: Math.max(0, Math.min(2 * Math.PI, x(cell.x1))),
  61. radius0: Math.max(0, y(cell.y0)),
  62. radius: Math.max(0, y(cell.y1)),
  63. depth: cell.depth,
  64. parent: cell.parent
  65. }, cell.data)]);
  66. }, []);
  67. }
  68. /**
  69. * Convert arc nodes into label rows.
  70. * Important to use mappedData rather than regular data, bc it is already unrolled
  71. * @param {Array} mappedData - Array of nodes.
  72. * @param {Object} accessors - object of accessors
  73. * @returns {Array} array of node for rendering as labels
  74. */
  75. function buildLabels(mappedData, accessors) {
  76. var getAngle = accessors.getAngle,
  77. getAngle0 = accessors.getAngle0,
  78. getLabel = accessors.getLabel,
  79. getRadius0 = accessors.getRadius0;
  80. return mappedData.filter(getLabel).map(function (row) {
  81. var truedAngle = -1 * getAngle(row) + Math.PI / 2;
  82. var truedAngle0 = -1 * getAngle0(row) + Math.PI / 2;
  83. var angle = (truedAngle0 + truedAngle) / 2;
  84. var rotateLabels = !row.dontRotateLabel;
  85. var rotAngle = -angle / (2 * Math.PI) * 360;
  86. return _extends({}, row, {
  87. children: null,
  88. angle: null,
  89. radius: null,
  90. x: getRadius0(row) * Math.cos(angle),
  91. y: getRadius0(row) * Math.sin(angle),
  92. style: _extends({
  93. textAnchor: rotAngle > 90 ? 'end' : 'start'
  94. }, row.labelStyle),
  95. rotation: rotateLabels ? rotAngle > 90 ? rotAngle + 180 : rotAngle === 90 ? 90 : rotAngle : null
  96. });
  97. });
  98. }
  99. var NOOP = function NOOP() {};
  100. function Sunburst(props) {
  101. var getAngle = props.getAngle,
  102. getAngle0 = props.getAngle0,
  103. animation = props.animation,
  104. className = props.className,
  105. children = props.children,
  106. data = props.data,
  107. height = props.height,
  108. hideRootNode = props.hideRootNode,
  109. getLabel = props.getLabel,
  110. width = props.width,
  111. getSize = props.getSize,
  112. colorType = props.colorType;
  113. var mappedData = getNodesToRender({
  114. data: data,
  115. height: height,
  116. hideRootNode: hideRootNode,
  117. width: width,
  118. getSize: getSize
  119. });
  120. var radialDomain = getRadialDomain(mappedData);
  121. var margin = getRadialLayoutMargin(width, height, radialDomain);
  122. var labelData = buildLabels(mappedData, {
  123. getAngle: getAngle,
  124. getAngle0: getAngle0,
  125. getLabel: getLabel,
  126. getRadius0: function getRadius0(d) {
  127. return d.radius0;
  128. }
  129. });
  130. var hofBuilder = function hofBuilder(f) {
  131. return function (e, i) {
  132. return f ? f(mappedData[e.index], i) : NOOP;
  133. };
  134. };
  135. return React.createElement(
  136. XYPlot,
  137. {
  138. height: height,
  139. hasTreeStructure: true,
  140. width: width,
  141. className: predefinedClassName + ' ' + className,
  142. margin: margin,
  143. xDomain: [-radialDomain, radialDomain],
  144. yDomain: [-radialDomain, radialDomain]
  145. },
  146. React.createElement(ArcSeries, _extends({
  147. colorType: colorType
  148. }, props, {
  149. animation: animation,
  150. radiusDomain: [0, radialDomain],
  151. // need to present a stripped down version for interpolation
  152. data: animation ? mappedData.map(function (row, index) {
  153. return _extends({}, row, {
  154. parent: null,
  155. children: null,
  156. index: index
  157. });
  158. }) : mappedData,
  159. _data: animation ? mappedData : null,
  160. arcClassName: predefinedClassName + '__series--radial__arc'
  161. }, LISTENERS_TO_OVERWRITE.reduce(function (acc, propName) {
  162. var prop = props[propName];
  163. acc[propName] = animation ? hofBuilder(prop) : prop;
  164. return acc;
  165. }, {}))),
  166. labelData.length > 0 && React.createElement(LabelSeries, { data: labelData, getLabel: getLabel }),
  167. children
  168. );
  169. }
  170. Sunburst.displayName = 'Sunburst';
  171. Sunburst.propTypes = {
  172. animation: AnimationPropType,
  173. getAngle: PropTypes.func,
  174. getAngle0: PropTypes.func,
  175. className: PropTypes.string,
  176. colorType: PropTypes.string,
  177. data: PropTypes.object.isRequired,
  178. height: PropTypes.number.isRequired,
  179. hideRootNode: PropTypes.bool,
  180. getLabel: PropTypes.func,
  181. onValueClick: PropTypes.func,
  182. onValueMouseOver: PropTypes.func,
  183. onValueMouseOut: PropTypes.func,
  184. getSize: PropTypes.func,
  185. width: PropTypes.number.isRequired,
  186. padAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number])
  187. };
  188. Sunburst.defaultProps = {
  189. getAngle: function getAngle(d) {
  190. return d.angle;
  191. },
  192. getAngle0: function getAngle0(d) {
  193. return d.angle0;
  194. },
  195. className: '',
  196. colorType: 'literal',
  197. getColor: function getColor(d) {
  198. return d.color;
  199. },
  200. hideRootNode: false,
  201. getLabel: function getLabel(d) {
  202. return d.label;
  203. },
  204. getSize: function getSize(d) {
  205. return d.size;
  206. },
  207. padAngle: 0
  208. };
  209. export default Sunburst;