@data-ui/histogram
Version:
React + d3 library for creating histograms
228 lines (196 loc) • 7.88 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _d3Array = require("d3-array");
var _theme = require("@data-ui/theme");
var _shape = require("@vx/shape");
var _curve = require("@vx/curve");
var _group = require("@vx/group");
var _scale = require("@vx/scale");
var _AnimatedDensitySeries = _interopRequireDefault(require("./animated/AnimatedDensitySeries"));
var _propShapes = require("../utils/propShapes");
var _kernelDensityEstimator = _interopRequireDefault(require("../utils/kernelDensityEstimator"));
var _epanechnikov = _interopRequireDefault(require("../utils/kernels/epanechnikov"));
var _gaussian = _interopRequireDefault(require("../utils/kernels/gaussian"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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); }
var propTypes = {
animated: _propTypes.default.bool,
rawData: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string])),
binnedData: _propShapes.binnedDataShape,
binType: _propTypes.default.oneOf(['numeric', 'categorical']),
fill: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string]),
fillOpacity: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.number]),
horizontal: _propTypes.default.bool,
kernel: _propTypes.default.oneOf(['gaussian', 'parabolic']),
showArea: _propTypes.default.bool,
showLine: _propTypes.default.bool,
smoothing: _propTypes.default.number,
stroke: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string]),
strokeDasharray: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string]),
strokeLinecap: _propTypes.default.oneOf(['butt', 'square', 'round', 'inherit']),
strokeWidth: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.number]),
useEntireScale: _propTypes.default.bool,
valueAccessor: _propTypes.default.func,
valueKey: _propTypes.default.string,
// likely injected by parent Histogram
binScale: _propTypes.default.func,
valueScale: _propTypes.default.func
};
var defaultProps = {
animated: true,
rawData: [],
binnedData: [],
binScale: null,
binType: null,
fill: _theme.chartTheme.colors.default,
fillOpacity: 0.3,
horizontal: false,
kernel: 'gaussian',
showArea: true,
showLine: true,
smoothing: 1,
stroke: _theme.chartTheme.colors.default,
strokeWidth: 2,
strokeDasharray: null,
strokeLinecap: 'round',
useEntireScale: false,
valueAccessor: function valueAccessor(d) {
return d;
},
valueKey: 'count',
valueScale: null
};
var BIN_OFFSET = 0.5;
var getBin = function getBin(d) {
return typeof d.bin === 'undefined' ? d.bin0 : d.bin;
};
var densityAccessor = function densityAccessor(d) {
return d.value;
};
var cumulativeAccessor = function cumulativeAccessor(d) {
return d.cumulative;
};
function DensitySeries(_ref) {
var animated = _ref.animated,
rawData = _ref.rawData,
binnedData = _ref.binnedData,
binScale = _ref.binScale,
binType = _ref.binType,
fill = _ref.fill,
fillOpacity = _ref.fillOpacity,
horizontal = _ref.horizontal,
kernel = _ref.kernel,
showArea = _ref.showArea,
showLine = _ref.showLine,
smoothing = _ref.smoothing,
stroke = _ref.stroke,
strokeWidth = _ref.strokeWidth,
strokeDasharray = _ref.strokeDasharray,
strokeLinecap = _ref.strokeLinecap,
useEntireScale = _ref.useEntireScale,
valueAccessor = _ref.valueAccessor,
valueKey = _ref.valueKey,
valueScale = _ref.valueScale;
if (!showArea && !showLine) return null;
var binWidth = binScale.bandwidth ? binScale.bandwidth() // categorical
: Math.abs(binScale(binnedData[0].bin1) - binScale(binnedData[0].bin0)); // numeric
var binOffset = BIN_OFFSET * binWidth * (horizontal && binType === 'numeric' ? -1 : 1); // all density estimators require numeric data, so if we're passed categorical data
// or pre-aggregated data, we just draw an area curve using the binned data
var densityScale = valueScale;
var getDensity = function getDensity(d) {
return d[valueKey];
};
var densityData = binnedData;
if (binType === 'numeric' && rawData.length > 0) {
// @TODO cache this with a non-functional component
var cumulative = /cumulative/gi.test(valueKey);
var bins = binnedData.map(getBin);
var kernelFunc = kernel === 'gaussian' ? (0, _gaussian.default)() : (0, _epanechnikov.default)(smoothing);
var estimator = (0, _kernelDensityEstimator.default)(kernelFunc, bins);
densityData = estimator(rawData.map(valueAccessor)); // area fills become inverted when the last value is less than the first value.
// padding with 0s ensures this never happens
densityData.unshift(_extends({}, densityData[0], {
value: 0
}));
densityData.push(_extends({}, densityData[densityData.length - 1], {
value: 0
}));
var densityRange = valueScale.range();
if (!useEntireScale) {
// set the range of the density scale to match the maximum data value
var maxVal = (0, _d3Array.max)(binnedData, function (d) {
return d[valueKey];
});
densityRange[1] = valueScale(maxVal);
}
densityScale = (0, _scale.scaleLinear)({
domain: (0, _d3Array.extent)(densityData, function (d, i) {
var val = densityAccessor(d); // compute cumulative in this loop
d.cumulative = val + (i > 0 ? densityData[i - 1].cumulative : 0);
d.id = i;
return cumulative ? d.cumulative : val;
}),
range: densityRange
});
getDensity = cumulative ? cumulativeAccessor : densityAccessor;
}
var offSetBinScale = binScale.copy();
offSetBinScale.range(binScale.range().map(function (v) {
return v + binOffset;
}));
var getX = horizontal ? getDensity : getBin;
var getY = horizontal ? getBin : getDensity;
var xScale = horizontal ? densityScale : offSetBinScale;
var yScale = horizontal ? offSetBinScale : densityScale;
return _react.default.createElement(_group.Group, {
style: {
pointerEvents: 'none'
}
}, animated && _react.default.createElement(_AnimatedDensitySeries.default, {
densityData: densityData,
fill: fill,
fillOpacity: fillOpacity,
horizontal: horizontal,
getX: getX,
getY: getY,
showArea: showArea,
showLine: showLine,
stroke: stroke,
strokeWidth: strokeWidth,
strokeDasharray: strokeDasharray,
strokeLinecap: strokeLinecap,
xScale: xScale,
yScale: yScale
}), !animated && showArea && _react.default.createElement(_shape.AreaClosed, {
data: densityData,
x: getX,
y: getY,
xScale: xScale,
yScale: yScale,
fill: fill,
fillOpacity: fillOpacity,
stroke: "transparent",
strokeWidth: strokeWidth,
curve: _curve.curveBasis
}), !animated && showLine && strokeWidth > 0 && _react.default.createElement(_shape.LinePath, {
data: densityData,
x: getX,
y: getY,
xScale: xScale,
yScale: yScale,
stroke: stroke,
strokeWidth: strokeWidth,
strokeDasharray: strokeDasharray,
strokeLinecap: strokeLinecap,
curve: _curve.curveBasis,
glyph: null
}));
}
DensitySeries.propTypes = propTypes;
DensitySeries.defaultProps = defaultProps;
DensitySeries.displayName = 'DensitySeries';
var _default = DensitySeries;
exports.default = _default;