lucid-ui
Version:
A UI component library from AppNexus.
284 lines (270 loc) • 12.9 kB
JavaScript
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;