UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

225 lines (205 loc) 9.79 kB
import _partial from "lodash/partial"; import _get from "lodash/get"; import _map from "lodash/map"; import _isEmpty from "lodash/isEmpty"; import _keys from "lodash/keys"; import _identity from "lodash/identity"; import _noop from "lodash/noop"; function _extends() { _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; }; return _extends.apply(this, arguments); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } import React from 'react'; import PropTypes from 'react-peek/prop-types'; import { lucidClassNames } from '../../util/style-helpers'; import { omitProps } from '../../util/component-types'; import * as d3Shape from 'd3-shape'; import * as chartConstants from '../../constants/charts'; import { buildModernHybridComponent } from '../../util/state-management'; import Line from '../../components/Line/Line'; import { ToolTipDumb as ToolTip } from '../../components/ToolTip/ToolTip'; import reducers from './PieChart.reducers'; var cx = lucidClassNames.bind('&-PieChart'); var string = PropTypes.string, number = PropTypes.number, arrayOf = PropTypes.arrayOf, bool = PropTypes.bool, shape = PropTypes.shape, object = PropTypes.object, func = PropTypes.func; var DONUT_WIDTH = 15; var HOVER_SCALE = 1.1; // duplicated in .less file var INNER_RADIUS = 0.5; var defaultProps = { height: 200, width: 200, // duplicated because `statics` aren't available during getDefaultProps margin: { top: 10, right: 10, bottom: 10, left: 10 }, palette: chartConstants.PALETTE_7, hasToolTips: true, hasStroke: true, isDonut: false, donutWidth: DONUT_WIDTH, ToolTip: ToolTip.defaultProps, isHovering: false, hoveringIndex: 0, onMouseOver: _noop, onMouseOut: _noop, xAxisField: 'x', xAxisFormatter: _identity, yAxisField: 'y', yAxisFormatter: _identity }; var PieChart = function PieChart(props) { var style = props.style, className = props.className, height = props.height, width = props.width, marginOriginal = props.margin, data = props.data, hasToolTips = props.hasToolTips, hasStroke = props.hasStroke, palette = props.palette, colorMap = props.colorMap, isDonut = props.isDonut, donutWidth = props.donutWidth, toolTipProps = props.ToolTip, isHovering = props.isHovering, hoveringIndex = props.hoveringIndex, xAxisField = props.xAxisField, xAxisFormatter = props.xAxisFormatter, yAxisField = props.yAxisField, yAxisFormatter = props.yAxisFormatter, passThroughs = _objectWithoutProperties(props, ["style", "className", "height", "width", "margin", "data", "hasToolTips", "hasStroke", "palette", "colorMap", "isDonut", "donutWidth", "ToolTip", "isHovering", "hoveringIndex", "xAxisField", "xAxisFormatter", "yAxisField", "yAxisFormatter"]); var margin = _objectSpread(_objectSpread({}, PieChart.MARGIN), marginOriginal); var svgClasses = cx(className, '&'); var pieChartProps = omitProps(omitProps(passThroughs, undefined, _keys(ToolTip.propTypes)), undefined, _keys(PieChart.propTypes)); // TODO: Consider displaying something specific when there is no data, // perhaps a loading indicator. if (_isEmpty(data) || width < 1 || height < 1) { return /*#__PURE__*/React.createElement("svg", _extends({}, pieChartProps, { style: style, className: svgClasses, width: width, height: height })); } var innerWidth = width - margin.left - margin.right; var innerHeight = height - margin.top - margin.bottom; var outerRadius = Math.min(innerWidth, innerHeight) / 2; var pie = d3Shape.pie().sort(null); // needed to put the slices in proper order var pieData = pie(_map(data, yAxisField)); var arc = d3Shape.arc().innerRadius(isDonut ? outerRadius - donutWidth : INNER_RADIUS).outerRadius(outerRadius); // Useful for capturing hovers when we're in donut mode var arcFull = d3Shape.arc().innerRadius(0).outerRadius(outerRadius); var handleMouseOut = function handleMouseOut(_ref) { var event = _ref.event; props.onMouseOut({ props: props, event: event }); }; var handleMouseOver = function handleMouseOver(index, event) { props.onMouseOver(index, { props: props, event: event }); }; return /*#__PURE__*/React.createElement("svg", _extends({}, pieChartProps, { style: style, className: svgClasses, width: width, height: height }), /*#__PURE__*/React.createElement(ToolTip, _extends({}, toolTipProps, { isLight: true, isExpanded: hasToolTips && isHovering, onMouseOver: _noop, onMouseOut: handleMouseOut }), /*#__PURE__*/React.createElement(ToolTip.Target, { elementType: "g" }, /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(margin.left, ", ").concat(margin.top, ")") }, /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(innerWidth / 2, ", ").concat(innerHeight / 2, ")") }, _map(pieData, function (pieDatum, index) { /* Even though innerRadius and outerRadius are set when constructing arc and arcFull, these functions still expect a type that includes innerRadius and outerRadius */ //@ts-ignore var arcFullData = arcFull(pieDatum); //@ts-ignore var arcData = arc(pieDatum); return /*#__PURE__*/React.createElement("g", { key: index, className: cx('&-slice-group', { '&-slice-group-is-hovering': isHovering && hoveringIndex === index }) }, /*#__PURE__*/React.createElement(Line, { key: index, className: cx('&-slice', { '&-slice-has-stroke': hasStroke }), d: arcData, color: _get(colorMap, data && data[index][xAxisField] || '', palette[index % palette.length]), transform: "scale(".concat(isHovering && hoveringIndex === index ? HOVER_SCALE : 1, ")") }), /*#__PURE__*/React.createElement("path", { className: cx('&-slice-hover'), d: arcFullData, transform: "scale(".concat(HOVER_SCALE, ")"), onMouseOver: _partial(handleMouseOver, index), onMouseOut: hasToolTips ? _noop : handleMouseOut })); })))), /*#__PURE__*/React.createElement(ToolTip.Title, null, xAxisFormatter(_get(data, "[".concat(hoveringIndex, "].").concat(xAxisField)))), /*#__PURE__*/React.createElement(ToolTip.Body, null, yAxisFormatter(_get(data, "[".concat(hoveringIndex, "].").concat(yAxisField)))))); }; PieChart.displayName = 'PieChart'; PieChart.propTypes = { style: object, className: string, height: number, width: number, margin: shape({ top: number, right: number, bottom: number, left: number }), data: arrayOf(object), hasToolTips: bool, hasStroke: bool, palette: arrayOf(string), colorMap: object, ToolTip: shape(ToolTip.propTypes), isDonut: bool, isHovering: bool, hoveringIndex: number, onMouseOver: func, onMouseOut: func, donutWidth: number, xAxisField: string, xAxisFormatter: func, yAxisField: string, yAxisFormatter: func }; PieChart.peek = { description: "\n\t\t\t\t\tPie charts are used for categorical data when you want to show the\n\t\t\t\t\trelative size of each category to the whole. We use similar \"x\" and \"y\"\n\t\t\t\t\tterms to keep parity with the other charts even though pie charts are\n\t\t\t\t\treally just key value based.\n\t\t\t\t", categories: ['visualizations', 'charts'], madeFrom: ['ToolTip'] }; PieChart.MARGIN = { top: 10, right: 10, bottom: 10, left: 10 }; PieChart.DONUT_WIDTH = DONUT_WIDTH; PieChart.HOVER_SCALE = HOVER_SCALE; PieChart.reducers = reducers; PieChart.defaultProps = defaultProps; export default buildModernHybridComponent(PieChart, { reducers: reducers }); export { PieChart as PieChartDumb };