UNPKG

@data-ui/histogram

Version:

React + d3 library for creating histograms

228 lines (196 loc) 7.88 kB
"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;