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.
 
 
 
 

418 lines
14 KiB

  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. 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; }; // Copyright (c) 2016 - 2017 Uber Technologies, Inc.
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. var _react = require('react');
  25. var _react2 = _interopRequireDefault(_react);
  26. var _propTypes = require('prop-types');
  27. var _propTypes2 = _interopRequireDefault(_propTypes);
  28. var _d3Scale = require('d3-scale');
  29. var _d3Format = require('d3-format');
  30. var _animation = require('../animation');
  31. var _xyPlot = require('../plot/xy-plot');
  32. var _xyPlot2 = _interopRequireDefault(_xyPlot);
  33. var _theme = require('../theme');
  34. var _chartUtils = require('../utils/chart-utils');
  35. var _markSeries = require('../plot/series/mark-series');
  36. var _markSeries2 = _interopRequireDefault(_markSeries);
  37. var _polygonSeries = require('../plot/series/polygon-series');
  38. var _polygonSeries2 = _interopRequireDefault(_polygonSeries);
  39. var _labelSeries = require('../plot/series/label-series');
  40. var _labelSeries2 = _interopRequireDefault(_labelSeries);
  41. var _decorativeAxis = require('../plot/axis/decorative-axis');
  42. var _decorativeAxis2 = _interopRequireDefault(_decorativeAxis);
  43. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  44. var predefinedClassName = 'rv-radar-chart';
  45. var DEFAULT_FORMAT = (0, _d3Format.format)('.2r');
  46. /**
  47. * Generate axes for each of the domains
  48. * @param {Object} props
  49. - props.animation {Boolean}
  50. - props.domains {Array} array of object specifying the way each axis is to be plotted
  51. - props.style {object} style object for the whole chart
  52. - props.tickFormat {Function} formatting function for axes
  53. - props.startingAngle {number} the initial angle offset
  54. * @return {Array} the plotted axis components
  55. */
  56. function getAxes(props) {
  57. var animation = props.animation,
  58. domains = props.domains,
  59. startingAngle = props.startingAngle,
  60. style = props.style,
  61. tickFormat = props.tickFormat,
  62. hideInnerMostValues = props.hideInnerMostValues;
  63. return domains.map(function (domain, index) {
  64. var angle = index / domains.length * Math.PI * 2 + startingAngle;
  65. var sortedDomain = domain.domain;
  66. var domainTickFormat = function domainTickFormat(t) {
  67. if (hideInnerMostValues && t === sortedDomain[0]) {
  68. return '';
  69. }
  70. return domain.tickFormat ? domain.tickFormat(t) : tickFormat(t);
  71. };
  72. return _react2.default.createElement(_decorativeAxis2.default, {
  73. animation: animation,
  74. key: index + '-axis',
  75. axisStart: { x: 0, y: 0 },
  76. axisEnd: {
  77. x: getCoordinate(Math.cos(angle)),
  78. y: getCoordinate(Math.sin(angle))
  79. },
  80. axisDomain: sortedDomain,
  81. numberOfTicks: 5,
  82. tickValue: domainTickFormat,
  83. style: style.axes
  84. });
  85. });
  86. }
  87. /**
  88. * Generate x or y coordinate for axisEnd
  89. * @param {Number} axisEndPoint
  90. - epsilon is an arbitrarily chosen small number to approximate axisEndPoints
  91. - to true values resulting from trigonometry functions (sin, cos) on angles
  92. * @return {Number} the x or y coordinate accounting for exact trig values
  93. */
  94. function getCoordinate(axisEndPoint) {
  95. var epsilon = 10e-13;
  96. if (Math.abs(axisEndPoint) <= epsilon) {
  97. axisEndPoint = 0;
  98. } else if (axisEndPoint > 0) {
  99. if (Math.abs(axisEndPoint - 0.5) <= epsilon) {
  100. axisEndPoint = 0.5;
  101. }
  102. } else if (axisEndPoint < 0) {
  103. if (Math.abs(axisEndPoint + 0.5) <= epsilon) {
  104. axisEndPoint = -0.5;
  105. }
  106. }
  107. return axisEndPoint;
  108. }
  109. /**
  110. * Generate labels for the ends of the axes
  111. * @param {Object} props
  112. - props.domains {Array} array of object specifying the way each axis is to be plotted
  113. - props.startingAngle {number} the initial angle offset
  114. - props.style {object} style object for just the labels
  115. * @return {Array} the prepped data for the labelSeries
  116. */
  117. function getLabels(props) {
  118. var domains = props.domains,
  119. startingAngle = props.startingAngle,
  120. style = props.style;
  121. return domains.map(function (_ref, index) {
  122. var name = _ref.name;
  123. var angle = index / domains.length * Math.PI * 2 + startingAngle;
  124. var radius = 1.2;
  125. return {
  126. x: radius * Math.cos(angle),
  127. y: radius * Math.sin(angle),
  128. label: name,
  129. style: style
  130. };
  131. });
  132. }
  133. /**
  134. * Generate the actual polygons to be plotted
  135. * @param {Object} props
  136. - props.animation {Boolean}
  137. - props.data {Array} array of object specifying what values are to be plotted
  138. - props.domains {Array} array of object specifying the way each axis is to be plotted
  139. - props.startingAngle {number} the initial angle offset
  140. - props.style {object} style object for the whole chart
  141. * @return {Array} the plotted axis components
  142. */
  143. function getPolygons(props) {
  144. var animation = props.animation,
  145. colorRange = props.colorRange,
  146. domains = props.domains,
  147. data = props.data,
  148. style = props.style,
  149. startingAngle = props.startingAngle,
  150. onSeriesMouseOver = props.onSeriesMouseOver,
  151. onSeriesMouseOut = props.onSeriesMouseOut;
  152. var scales = domains.reduce(function (acc, _ref2) {
  153. var domain = _ref2.domain,
  154. name = _ref2.name;
  155. acc[name] = (0, _d3Scale.scaleLinear)().domain(domain).range([0, 1]);
  156. return acc;
  157. }, {});
  158. return data.map(function (row, rowIndex) {
  159. var mappedData = domains.map(function (_ref3, index) {
  160. var name = _ref3.name,
  161. getValue = _ref3.getValue;
  162. var dataPoint = getValue ? getValue(row) : row[name];
  163. // error handling if point doesn't exist
  164. var angle = index / domains.length * Math.PI * 2 + startingAngle;
  165. // dont let the radius become negative
  166. var radius = Math.max(scales[name](dataPoint), 0);
  167. return { x: radius * Math.cos(angle), y: radius * Math.sin(angle), name: row.name };
  168. });
  169. return _react2.default.createElement(_polygonSeries2.default, {
  170. animation: animation,
  171. className: predefinedClassName + '-polygon',
  172. key: rowIndex + '-polygon',
  173. data: mappedData,
  174. style: _extends({
  175. stroke: row.color || row.stroke || colorRange[rowIndex % colorRange.length],
  176. fill: row.color || row.fill || colorRange[rowIndex % colorRange.length]
  177. }, style.polygons),
  178. onSeriesMouseOver: onSeriesMouseOver,
  179. onSeriesMouseOut: onSeriesMouseOut
  180. });
  181. });
  182. }
  183. /**
  184. * Generate circles at the polygon points for Hover functionality
  185. * @param {Object} props
  186. - props.animation {Boolean}
  187. - props.data {Array} array of object specifying what values are to be plotted
  188. - props.domains {Array} array of object specifying the way each axis is to be plotted
  189. - props.startingAngle {number} the initial angle offset
  190. - props.style {object} style object for the whole chart
  191. - props.onValueMouseOver {function} function to call on mouse over a polygon point
  192. - props.onValueMouseOver {function} function to call when mouse leaves a polygon point
  193. * @return {Array} the plotted axis components
  194. */
  195. function getPolygonPoints(props) {
  196. var animation = props.animation,
  197. domains = props.domains,
  198. data = props.data,
  199. startingAngle = props.startingAngle,
  200. style = props.style,
  201. onValueMouseOver = props.onValueMouseOver,
  202. onValueMouseOut = props.onValueMouseOut;
  203. if (!onValueMouseOver) {
  204. return;
  205. }
  206. var scales = domains.reduce(function (acc, _ref4) {
  207. var domain = _ref4.domain,
  208. name = _ref4.name;
  209. acc[name] = (0, _d3Scale.scaleLinear)().domain(domain).range([0, 1]);
  210. return acc;
  211. }, {});
  212. return data.map(function (row, rowIndex) {
  213. var mappedData = domains.map(function (_ref5, index) {
  214. var name = _ref5.name,
  215. getValue = _ref5.getValue;
  216. var dataPoint = getValue ? getValue(row) : row[name];
  217. // error handling if point doesn't exist
  218. var angle = index / domains.length * Math.PI * 2 + startingAngle;
  219. // dont let the radius become negative
  220. var radius = Math.max(scales[name](dataPoint), 0);
  221. return {
  222. x: radius * Math.cos(angle),
  223. y: radius * Math.sin(angle),
  224. domain: name,
  225. value: dataPoint,
  226. dataName: row.name
  227. };
  228. });
  229. return _react2.default.createElement(_markSeries2.default, {
  230. animation: animation,
  231. className: predefinedClassName + '-polygonPoint',
  232. key: rowIndex + '-polygonPoint',
  233. data: mappedData,
  234. size: 10,
  235. style: _extends({}, style.polygons, {
  236. fill: 'transparent',
  237. stroke: 'transparent'
  238. }),
  239. onValueMouseOver: onValueMouseOver,
  240. onValueMouseOut: onValueMouseOut
  241. });
  242. });
  243. }
  244. function RadarChart(props) {
  245. var animation = props.animation,
  246. className = props.className,
  247. children = props.children,
  248. colorRange = props.colorRange,
  249. data = props.data,
  250. domains = props.domains,
  251. height = props.height,
  252. hideInnerMostValues = props.hideInnerMostValues,
  253. margin = props.margin,
  254. onMouseLeave = props.onMouseLeave,
  255. onMouseEnter = props.onMouseEnter,
  256. startingAngle = props.startingAngle,
  257. style = props.style,
  258. tickFormat = props.tickFormat,
  259. width = props.width,
  260. renderAxesOverPolygons = props.renderAxesOverPolygons,
  261. onValueMouseOver = props.onValueMouseOver,
  262. onValueMouseOut = props.onValueMouseOut,
  263. onSeriesMouseOver = props.onSeriesMouseOver,
  264. onSeriesMouseOut = props.onSeriesMouseOut;
  265. var axes = getAxes({
  266. domains: domains,
  267. animation: animation,
  268. hideInnerMostValues: hideInnerMostValues,
  269. startingAngle: startingAngle,
  270. style: style,
  271. tickFormat: tickFormat
  272. });
  273. var polygons = getPolygons({
  274. animation: animation,
  275. colorRange: colorRange,
  276. domains: domains,
  277. data: data,
  278. startingAngle: startingAngle,
  279. style: style,
  280. onSeriesMouseOver: onSeriesMouseOver,
  281. onSeriesMouseOut: onSeriesMouseOut
  282. });
  283. var polygonPoints = getPolygonPoints({
  284. animation: animation,
  285. colorRange: colorRange,
  286. domains: domains,
  287. data: data,
  288. startingAngle: startingAngle,
  289. style: style,
  290. onValueMouseOver: onValueMouseOver,
  291. onValueMouseOut: onValueMouseOut
  292. });
  293. var labelSeries = _react2.default.createElement(_labelSeries2.default, {
  294. animation: animation,
  295. key: className,
  296. className: predefinedClassName + '-label',
  297. data: getLabels({ domains: domains, style: style.labels, startingAngle: startingAngle }) });
  298. return _react2.default.createElement(
  299. _xyPlot2.default,
  300. {
  301. height: height,
  302. width: width,
  303. margin: margin,
  304. dontCheckIfEmpty: true,
  305. className: className + ' ' + predefinedClassName,
  306. onMouseLeave: onMouseLeave,
  307. onMouseEnter: onMouseEnter,
  308. xDomain: [-1, 1],
  309. yDomain: [-1, 1] },
  310. children,
  311. !renderAxesOverPolygons && axes.concat(polygons).concat(labelSeries).concat(polygonPoints),
  312. renderAxesOverPolygons && polygons.concat(labelSeries).concat(axes).concat(polygonPoints)
  313. );
  314. }
  315. RadarChart.displayName = 'RadarChart';
  316. RadarChart.propTypes = {
  317. animation: _animation.AnimationPropType,
  318. className: _propTypes2.default.string,
  319. colorType: _propTypes2.default.string,
  320. colorRange: _propTypes2.default.arrayOf(_propTypes2.default.string),
  321. data: _propTypes2.default.arrayOf(_propTypes2.default.object).isRequired,
  322. domains: _propTypes2.default.arrayOf(_propTypes2.default.shape({
  323. name: _propTypes2.default.string.isRequired,
  324. domain: _propTypes2.default.arrayOf(_propTypes2.default.number).isRequired,
  325. tickFormat: _propTypes2.default.func
  326. })).isRequired,
  327. height: _propTypes2.default.number.isRequired,
  328. hideInnerMostValues: _propTypes2.default.bool,
  329. margin: _chartUtils.MarginPropType,
  330. startingAngle: _propTypes2.default.number,
  331. style: _propTypes2.default.shape({
  332. axes: _propTypes2.default.object,
  333. labels: _propTypes2.default.object,
  334. polygons: _propTypes2.default.object
  335. }),
  336. tickFormat: _propTypes2.default.func,
  337. width: _propTypes2.default.number.isRequired,
  338. renderAxesOverPolygons: _propTypes2.default.bool,
  339. onValueMouseOver: _propTypes2.default.func,
  340. onValueMouseOut: _propTypes2.default.func,
  341. onSeriesMouseOver: _propTypes2.default.func,
  342. onSeriesMouseOut: _propTypes2.default.func
  343. };
  344. RadarChart.defaultProps = {
  345. className: '',
  346. colorType: 'category',
  347. colorRange: _theme.DISCRETE_COLOR_RANGE,
  348. hideInnerMostValues: true,
  349. startingAngle: Math.PI / 2,
  350. style: {
  351. axes: {
  352. line: {},
  353. ticks: {},
  354. text: {}
  355. },
  356. labels: {
  357. fontSize: 10,
  358. textAnchor: 'middle'
  359. },
  360. polygons: {
  361. strokeWidth: 0.5,
  362. strokeOpacity: 1,
  363. fillOpacity: 0.1
  364. }
  365. },
  366. tickFormat: DEFAULT_FORMAT,
  367. renderAxesOverPolygons: false
  368. };
  369. exports.default = RadarChart;