UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

284 lines (270 loc) 12.9 kB
import _isString from "lodash/isString"; import _get from "lodash/get"; import _keys from "lodash/keys"; import _isEmpty from "lodash/isEmpty"; import _map from "lodash/map"; import _identity from "lodash/identity"; 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, getFirst } from '../../util/component-types'; import { maxByFields, maxByFieldsStacked } from '../../util/chart-helpers'; import * as d3Scale from 'd3-scale'; import * as chartConstants from '../../constants/charts'; import Axis from '../Axis/Axis'; import AxisLabel from '../AxisLabel/AxisLabel'; import Bars from '../Bars/Bars'; import ContextMenu from '../ContextMenu/ContextMenu'; import Legend from '../Legend/Legend'; import EmptyStateWrapper from '../EmptyStateWrapper/EmptyStateWrapper'; var cx = lucidClassNames.bind('&-BarChart'); var arrayOf = PropTypes.arrayOf, func = PropTypes.func, number = PropTypes.number, object = PropTypes.object, shape = PropTypes.shape, string = PropTypes.string, array = PropTypes.array, bool = PropTypes.bool, oneOfType = PropTypes.oneOfType, oneOf = PropTypes.oneOf; var defaultProps = { height: 400, width: 1000, // duplicated because `statics` aren't available during getDefaultProps margin: { top: 10, right: 20, bottom: 50, left: 80 }, palette: chartConstants.PALETTE_7, hasToolTips: true, hasLegend: false, renderTooltipBody: null, xAxisField: 'x', xAxisTickCount: null, xAxisTitle: null, xAxisTitleColor: '#000', xAxisFormatter: _identity, xAxisTextOrientation: 'horizontal', yAxisFields: ['y'], yAxisTickCount: null, yAxisIsStacked: false, yAxisMin: 0, yAxisTitle: null, yAxisTitleColor: '#000', yAxisTooltipFormatter: function yAxisTooltipFormatter(yField, yValueFormatted) { return "".concat(yField, ": ").concat(yValueFormatted); }, yAxisTextOrientation: 'horizontal' }; export var BarChart = function BarChart(props) { var className = props.className, height = props.height, width = props.width, marginOriginal = props.margin, data = props.data, legend = props.legend, isLoading = props.isLoading, hasToolTips = props.hasToolTips, hasLegend = props.hasLegend, palette = props.palette, colorMap = props.colorMap, renderTooltipBody = props.renderTooltipBody, xAxisField = props.xAxisField, xAxisFormatter = props.xAxisFormatter, xAxisTitle = props.xAxisTitle, xAxisTitleColor = props.xAxisTitleColor, xAxisTickCount = props.xAxisTickCount, xAxisTextOrientation = props.xAxisTextOrientation, yAxisFields = props.yAxisFields, yAxisFormatter = props.yAxisFormatter, yAxisTitle = props.yAxisTitle, yAxisTitleColor = props.yAxisTitleColor, yAxisIsStacked = props.yAxisIsStacked, yAxisTickCount = props.yAxisTickCount, yAxisMin = props.yAxisMin, yAxisTooltipFormatter = props.yAxisTooltipFormatter, yAxisTooltipDataFormatter = props.yAxisTooltipDataFormatter, _props$yAxisMax = props.yAxisMax, yAxisMax = _props$yAxisMax === void 0 ? yAxisIsStacked ? maxByFieldsStacked(data, yAxisFields) : maxByFields(data, yAxisFields) : _props$yAxisMax, yAxisTextOrientation = props.yAxisTextOrientation, passThroughs = _objectWithoutProperties(props, ["className", "height", "width", "margin", "data", "legend", "isLoading", "hasToolTips", "hasLegend", "palette", "colorMap", "renderTooltipBody", "xAxisField", "xAxisFormatter", "xAxisTitle", "xAxisTitleColor", "xAxisTickCount", "xAxisTextOrientation", "yAxisFields", "yAxisFormatter", "yAxisTitle", "yAxisTitleColor", "yAxisIsStacked", "yAxisTickCount", "yAxisMin", "yAxisTooltipFormatter", "yAxisTooltipDataFormatter", "yAxisMax", "yAxisTextOrientation"]); var margin = _objectSpread(_objectSpread({}, BarChart.MARGIN), marginOriginal); var svgClasses = cx(className, '&'); var innerWidth = width - margin.left - margin.right; var innerHeight = height - margin.top - margin.bottom; // `paddingInner` determines the space between the bars or groups of bars var paddingInner = yAxisFields.length > 1 ? BarChart.PADDING_GROUPED_OR_STACKED : BarChart.PADDING; var xScale = d3Scale.scaleBand().domain(_map(data, xAxisField)).range([0, innerWidth]).paddingInner(paddingInner).paddingOuter(0.5); var yScale = d3Scale.scaleLinear().domain([yAxisMin, yAxisMax]).range([innerHeight, 0]); // @ts-ignore var xAxisFinalFormatter = xAxisFormatter || xScale.tickFormat(); var yAxisFinalFormatter = yAxisFormatter || yScale.tickFormat(); var yFinalFormatter = yAxisTooltipDataFormatter ? yAxisTooltipDataFormatter : yAxisFinalFormatter; if (_isEmpty(data) || width < 1 || height < 1 || isLoading) { var emptyStateWrapper = getFirst(props, BarChart.EmptyStateWrapper) || /*#__PURE__*/React.createElement(BarChart.EmptyStateWrapper, { Title: "You have no data." }); return /*#__PURE__*/React.createElement(EmptyStateWrapper, _extends({}, emptyStateWrapper.props, { isEmpty: _isEmpty(data), isLoading: isLoading }), emptyStateWrapper.props.children, /*#__PURE__*/React.createElement("svg", _extends({}, omitProps(passThroughs, undefined, _keys(BarChart.propTypes)), { className: svgClasses, width: width, height: height }), /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(margin.left, ", ").concat(innerHeight + margin.top, ")") }, /*#__PURE__*/React.createElement(Axis, { orient: "bottom", scale: xScale, tickCount: xAxisTickCount })), /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(margin.left, ", ").concat(margin.top, ")") }, /*#__PURE__*/React.createElement(Axis, { orient: "left", scale: yScale, tickFormat: yFinalFormatter, tickCount: yAxisTickCount })))); } return /*#__PURE__*/React.createElement("svg", _extends({}, omitProps(passThroughs, undefined, _keys(BarChart.propTypes)), { className: svgClasses, width: width, height: height }), /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(margin.left, ", ").concat(innerHeight + margin.top, ")") }, /*#__PURE__*/React.createElement(Axis, { orient: "bottom" // @ts-ignore , scale: xScale, outerTickSize: 0, tickFormat: xAxisFinalFormatter, tickCount: xAxisTickCount, textOrientation: xAxisTextOrientation }), hasLegend ? /*#__PURE__*/React.createElement(ContextMenu, { direction: "down", alignment: "center", directonOffset: (margin.bottom / 2 + Legend.HEIGHT / 2) * -1 /* should center the legend in the bottom margin */ }, /*#__PURE__*/React.createElement(ContextMenu.Target, { elementType: "g" }, /*#__PURE__*/React.createElement("rect", { className: cx('&-invisible'), width: innerWidth, height: margin.bottom })), /*#__PURE__*/React.createElement(ContextMenu.FlyOut, { className: cx('&-legend-container') }, /*#__PURE__*/React.createElement(Legend, { orient: "horizontal" }, _map(yAxisFields, function (field, index) { return /*#__PURE__*/React.createElement(Legend.Item, { key: index, hasPoint: true, hasLine: false, color: _get(colorMap, field, palette[index % palette.length]), pointKind: 1 }, _get(legend, field, field)); })))) : null), xAxisTitle ? /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(margin.left, ", ").concat(margin.top + innerHeight, ")") }, /*#__PURE__*/React.createElement(AxisLabel, { orient: "bottom", width: innerWidth, height: margin.bottom, label: xAxisTitle, color: _isString(xAxisTitleColor) ? xAxisTitleColor : palette[xAxisTitleColor % palette.length] })) : null, /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(margin.left, ", ").concat(margin.top, ")") }, /*#__PURE__*/React.createElement(Axis, { orient: "left", scale: yScale, tickFormat: yAxisFinalFormatter, tickCount: yAxisTickCount, textOrientation: yAxisTextOrientation })), yAxisTitle ? /*#__PURE__*/React.createElement("g", { transform: "translate(0, ".concat(margin.top, ")") }, /*#__PURE__*/React.createElement(AxisLabel, { orient: "left", width: margin.left, height: innerHeight, label: yAxisTitle, color: _isString(yAxisTitleColor) ? yAxisTitleColor : palette[yAxisTitleColor % palette.length] })) : null, /*#__PURE__*/React.createElement("g", { transform: "translate(".concat(margin.left, ", ").concat(margin.top, ")") }, /*#__PURE__*/React.createElement(Bars, { xField: xAxisField, xScale: xScale, xFormatter: xAxisFormatter, yFields: yAxisFields, yScale: yScale // @ts-ignore , yFormatter: yFinalFormatter, yStackedMax: yAxisMax, data: data, isStacked: yAxisIsStacked, yTooltipFormatter: yAxisTooltipFormatter, hasToolTips: hasToolTips, legend: legend, palette: palette, colorMap: colorMap, renderTooltipBody: renderTooltipBody }))); }; BarChart.displayName = 'BarChart'; BarChart.propTypes = { className: string, height: number, width: number, margin: shape({ top: number, right: number, bottom: number, left: number }), data: arrayOf(object), legend: object, isLoading: bool, hasToolTips: bool, hasLegend: bool, palette: arrayOf(string), colorMap: object, xAxisField: string, xAxisTickCount: number, xAxisFormatter: func, xAxisTitle: string, xAxisTitleColor: oneOfType([number, string]), yAxisFields: array, yAxisMin: number, yAxisMax: number, yAxisFormatter: func, yAxisIsStacked: bool, yAxisTickCount: number, yAxisTitle: string, yAxisTitleColor: oneOfType([number, string]), yAxisTooltipFormatter: func, yAxisTooltipDataFormatter: func, renderTooltipBody: func, xAxisTextOrientation: oneOf(['vertical', 'horizontal', 'diagonal']), yAxisTextOrientation: oneOf(['vertical', 'horizontal', 'diagonal']) }; BarChart.defaultProps = defaultProps; BarChart.peek = { description: "\n\t\tBar charts are great for showing data that fits neatly in to \"buckets\".\n\t\tThe x axis data must be strings, and the y axis data must be numeric.\n\t", categories: ['visualizations', 'charts'], madeFrom: ['ContextMenu', 'ToolTip'] }; BarChart.EmptyStateWrapper = EmptyStateWrapper; BarChart.PADDING = 0.05; BarChart.PADDING_GROUPED_OR_STACKED = 0.3; BarChart.MARGIN = { top: 10, right: 20, bottom: 50, left: 80 }; export default BarChart;