victory-core
Version:
374 lines (355 loc) • 16.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createDomainFunction = createDomainFunction;
exports.formatDomain = formatDomain;
exports.getDomain = getDomain;
exports.getDomainFromCategories = getDomainFromCategories;
exports.getDomainFromData = getDomainFromData;
exports.getDomainFromMinMax = getDomainFromMinMax;
exports.getDomainFromProps = getDomainFromProps;
exports.getDomainWithZero = getDomainWithZero;
exports.getMaxFromProps = getMaxFromProps;
exports.getMinFromProps = getMinFromProps;
exports.getSymmetricDomain = getSymmetricDomain;
exports.isDomainComponent = isDomainComponent;
var _react = _interopRequireDefault(require("react"));
var _isDate = _interopRequireDefault(require("lodash/isDate"));
var _isPlainObject = _interopRequireDefault(require("lodash/isPlainObject"));
var _sortedUniq = _interopRequireDefault(require("lodash/sortedUniq"));
var Data = _interopRequireWildcard(require("./data"));
var Scale = _interopRequireWildcard(require("./scale"));
var Helpers = _interopRequireWildcard(require("./helpers"));
var Collection = _interopRequireWildcard(require("./collection"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Private Methods
function cleanDomain(domain, props, axis) {
const scaleType = Scale.getScaleType(props, axis);
if (scaleType !== "log") {
return domain;
}
const rules = dom => {
const almostZero = dom[0] < 0 || dom[1] < 0 ? -1 / Number.MAX_SAFE_INTEGER : 1 / Number.MAX_SAFE_INTEGER;
const domainOne = dom[0] === 0 ? almostZero : dom[0];
const domainTwo = dom[1] === 0 ? almostZero : dom[1];
return [domainOne, domainTwo];
};
return rules(domain);
}
function getDomainPadding(props, axis) {
const formatPadding = padding => {
return Array.isArray(padding) ? {
left: padding[0],
right: padding[1]
} : {
left: padding,
right: padding
};
};
return (0, _isPlainObject.default)(props.domainPadding) ? formatPadding(props.domainPadding[axis]) : formatPadding(props.domainPadding);
}
function getFlatData(dataset, axis) {
const axisKey = `_${axis}`;
return dataset.flat().map(datum => {
return datum[axisKey] && datum[axisKey][1] !== undefined ? datum[axisKey][1] : datum[axisKey];
});
}
function getExtremeFromData(dataset, axis, type) {
if (type === void 0) {
type = "min";
}
const getExtreme = arr => type === "max" ? Math.max(...arr) : Math.min(...arr);
const initialValue = type === "max" ? -Infinity : Infinity;
let containsDate = false;
const result = dataset.flat().reduce((memo, datum) => {
const current0 = datum[`_${axis}0`] !== undefined ? datum[`_${axis}0`] : datum[`_${axis}`];
const current1 = datum[`_${axis}1`] !== undefined ? datum[`_${axis}1`] : datum[`_${axis}`];
const current = getExtreme([current0, current1]);
containsDate = containsDate || current0 instanceof Date || current1 instanceof Date;
return getExtreme([memo, current]);
}, initialValue);
return containsDate ? new Date(result) : result;
}
function padDomain(domain, props, axis) {
if (!props.domainPadding) {
return domain;
}
const minDomain = getMinFromProps(props, axis);
const maxDomain = getMaxFromProps(props, axis);
const padding = getDomainPadding(props, axis);
if (!padding.left && !padding.right) {
return domain;
}
const min = Collection.getMinValue(domain);
const max = Collection.getMaxValue(domain);
const currentAxis = Helpers.getCurrentAxis(axis, props.horizontal);
const range = Helpers.getRange(props, currentAxis);
const rangeExtent = Math.abs(range[0] - range[1]);
const paddedRangeExtent = Math.max(rangeExtent - padding.left - padding.right, 1);
const paddedDomainExtent = Math.abs(max.valueOf() - min.valueOf()) / paddedRangeExtent * rangeExtent;
const simplePadding = {
left: paddedDomainExtent * padding.left / rangeExtent,
right: paddedDomainExtent * padding.right / rangeExtent
};
let paddedDomain = {
min: min.valueOf() - simplePadding.left,
max: max.valueOf() + simplePadding.right
};
const singleQuadrantDomainPadding = (0, _isPlainObject.default)(props.singleQuadrantDomainPadding) ? props.singleQuadrantDomainPadding[axis] : props.singleQuadrantDomainPadding;
const addsQuadrants = min.valueOf() >= 0 && paddedDomain.min <= 0 || max.valueOf() <= 0 && paddedDomain.max >= 0;
const adjust = (val, type) => {
const coerce = type === "min" && min.valueOf() >= 0 && val <= 0 || type === "max" && max.valueOf() <= 0 && val >= 0;
return coerce ? 0 : val;
};
if (addsQuadrants && singleQuadrantDomainPadding !== false) {
// Naive initial padding calculation
const initialPadding = {
// @ts-expect-error `max/min` might be dates
left: Math.abs(max - min) * padding.left / rangeExtent,
// @ts-expect-error `max/min` might be dates
right: Math.abs(max - min) * padding.right / rangeExtent
};
// Adjust the domain by the initial padding
const adjustedDomain = {
min: adjust(min.valueOf() - initialPadding.left, "min"),
max: adjust(max.valueOf() + initialPadding.right, "max")
};
// re-calculate padding, taking the adjusted domain into account
const finalPadding = {
left: Math.abs(adjustedDomain.max - adjustedDomain.min) * padding.left / rangeExtent,
right: Math.abs(adjustedDomain.max - adjustedDomain.min) * padding.right / rangeExtent
};
// Adjust the domain by the final padding
paddedDomain = {
min: adjust(min.valueOf() - finalPadding.left, "min"),
max: adjust(max.valueOf() + finalPadding.right, "max")
};
}
// default to minDomain / maxDomain if they exist
const finalDomain = {
min: minDomain !== undefined ? minDomain : paddedDomain.min,
max: maxDomain !== undefined ? maxDomain : paddedDomain.max
};
return min instanceof Date || max instanceof Date ? getDomainFromMinMax(new Date(finalDomain.min), new Date(finalDomain.max)) : getDomainFromMinMax(finalDomain.min, finalDomain.max);
}
// Public Methods
/**
* Returns a getDomain function
* @param {Function} getDomainFromDataFunction: a function that takes props and axis and
* returns a domain based on data
* @param {Function} formatDomainFunction: a function that takes domain, props, and axis and
* returns a formatted domain
* @returns {Function} a function that takes props and axis and returns a formatted domain
*/
function createDomainFunction(getDomainFromDataFunction, formatDomainFunction) {
const getDomainFromDataFn = Helpers.isFunction(getDomainFromDataFunction) ? getDomainFromDataFunction : getDomainFromData;
const formatDomainFn = Helpers.isFunction(formatDomainFunction) ? formatDomainFunction : formatDomain;
return (props, axis) => {
const propsDomain = getDomainFromProps(props, axis);
if (propsDomain) {
return formatDomainFn(propsDomain, props, axis);
}
const categories = Data.getCategories(props, axis);
const domain = categories ? getDomainFromCategories(props, axis, categories) : getDomainFromDataFn(props, axis);
return domain ? formatDomainFn(domain, props, axis) : undefined;
};
}
/**
* Returns a formatted domain.
* @param {Array} domain: a domain in the form of a two element array
* @param {Object} props: the props object
* @param {String} axis: the current axis
* @returns {Array} a domain in the form of a two element array
*/
function formatDomain(domain, props, axis) {
return cleanDomain(padDomain(domain, props, axis), props, axis);
}
/**
* Returns a domain for a given axis based on props, category, or data
* @param {Object} props: the props object
* @param {String} axis: the current axis
* @returns {Array} the domain for the given axis
*/
function getDomain(props, axis) {
return createDomainFunction()(props, axis);
}
/**
* Returns a domain based on categories if they exist
* @param {Object} props: the props object
* @param {String} axis: the current axis
* @param {Array} categories: an array of categories corresponding to a given axis
* @returns {Array|undefined} returns a domain from categories or undefined
*/
function getDomainFromCategories(props, axis, categories) {
const categoriesArray = categories || Data.getCategories(props, axis);
const {
polar,
startAngle = 0,
endAngle = 360
} = props;
if (!categoriesArray) {
return undefined;
}
const minDomain = getMinFromProps(props, axis);
const maxDomain = getMaxFromProps(props, axis);
const stringArray = Collection.containsStrings(categoriesArray) ? Data.getStringsFromCategories(props, axis) : [];
const stringMap = stringArray.length === 0 ? null : stringArray.reduce((memo, string, index) => {
memo[string] = index + 1;
return memo;
}, {});
const categoryValues = stringMap ? categoriesArray.map(value => stringMap[value]) : categoriesArray;
const min = minDomain !== undefined ? minDomain : Collection.getMinValue(categoryValues);
const max = maxDomain !== undefined ? maxDomain : Collection.getMaxValue(categoryValues);
const categoryDomain = getDomainFromMinMax(min, max);
return polar && axis === "x" && Math.abs(startAngle - endAngle) === 360 ? getSymmetricDomain(categoryDomain, categoryValues) : categoryDomain;
}
/**
* Returns a domain from a dataset for a given axis
* @param {Object} props: the props object
* @param {String} axis: the current axis
* @param {Array} dataset: an array of data
* @returns {Array} the domain based on data
*/
function getDomainFromData(props, axis, dataset) {
const datasetArray = dataset || Data.getData(props);
const {
polar,
startAngle = 0,
endAngle = 360
} = props;
const minDomain = getMinFromProps(props, axis);
const maxDomain = getMaxFromProps(props, axis);
if (datasetArray.length < 1) {
return minDomain !== undefined && maxDomain !== undefined ? getDomainFromMinMax(minDomain, maxDomain) : undefined;
}
const min = minDomain !== undefined ? minDomain : getExtremeFromData(datasetArray, axis, "min");
const max = maxDomain !== undefined ? maxDomain : getExtremeFromData(datasetArray, axis, "max");
const domain = getDomainFromMinMax(min, max);
return polar && axis === "x" && Math.abs(startAngle - endAngle) === 360 ? getSymmetricDomain(domain, getFlatData(datasetArray, axis)) : domain;
}
/**
* Returns a domain in the form of a two element array given a min and max value.
* @param {Number|Date} min: the props object
* @param {Number|Date} max: the current axis
* @returns {Array} the minDomain based on props
*/
function getDomainFromMinMax(min, max) {
const getSinglePointDomain = val => {
// d3-scale does not properly resolve very small differences.
const verySmallNumber =
// eslint-disable-next-line no-magic-numbers
val === 0 ? 2 * Math.pow(10, -10) : Math.pow(10, -10);
const verySmallDate = 1;
const minVal = val instanceof Date ? new Date(Number(val) - verySmallDate) : Number(val) - verySmallNumber;
const maxVal = val instanceof Date ? new Date(Number(val) + verySmallDate) : Number(val) + verySmallNumber;
return val === 0 ? [0, maxVal] : [minVal, maxVal];
};
return Number(min) === Number(max) ? getSinglePointDomain(max) : [min, max];
}
/**
* Returns a the domain for a given axis if domain is given in props
* @param {Object} props: the props object
* @param {String} axis: the current axis
* @returns {Array|undefined} the domain based on props
*/
function getDomainFromProps(props, axis) {
const minDomain = getMinFromProps(props, axis);
const maxDomain = getMaxFromProps(props, axis);
if ((0, _isPlainObject.default)(props.domain) && props.domain[axis]) {
return props.domain[axis];
} else if (Array.isArray(props.domain)) {
return props.domain;
} else if (minDomain !== undefined && maxDomain !== undefined) {
return getDomainFromMinMax(minDomain, maxDomain);
}
return undefined;
}
/**
* Returns a domain for a given axis. This method forces the domain to include
* zero unless the domain is explicitly specified in props.
* @param {Object} props: the props object
* @param {String} axis: the current axis
* @returns {Array} the domain for the given axis
*/
function getDomainWithZero(props, axis) {
const propsDomain = getDomainFromProps(props, axis);
if (propsDomain) {
return propsDomain;
}
const dataset = Data.getData(props);
const y0Min = dataset.reduce((min, datum) => datum._y0 < min ? datum._y0 : min, Infinity);
const ensureZero = domain => {
if (axis === "x") {
return domain;
}
const defaultMin = y0Min !== Infinity ? y0Min : 0;
const maxDomainProp = getMaxFromProps(props, axis);
const minDomainProp = getMinFromProps(props, axis);
const max = maxDomainProp !== undefined ? maxDomainProp : Collection.getMaxValue(domain, defaultMin);
const min = minDomainProp !== undefined ? minDomainProp : Collection.getMinValue(domain, defaultMin);
return getDomainFromMinMax(min, max);
};
const getDomainFunction = () => {
return getDomainFromData(props, axis, dataset);
};
const formatDomainFunction = domain => {
return formatDomain(ensureZero(domain), props, axis);
};
return createDomainFunction(getDomainFunction, formatDomainFunction)(props, axis);
}
/**
* Returns the maxDomain from props if it exists
* @param {Object} props: the props object
* @param {String} axis: the current axis
* @returns {Number|Date|undefined} the maxDomain based on props
*/
function getMaxFromProps(props, axis) {
if ((0, _isPlainObject.default)(props.maxDomain) && props.maxDomain[axis] !== undefined) {
return props.maxDomain[axis];
}
return typeof props.maxDomain === "number" || (0, _isDate.default)(props.maxDomain) ? props.maxDomain : undefined;
}
/**
* Returns the minDomain from props if it exists
* @param {Object} props: the props object
* @param {String} axis: the current axis
* @returns {Number|Date|undefined} the minDomain based on props
*/
function getMinFromProps(props, axis) {
if ((0, _isPlainObject.default)(props.minDomain) && props.minDomain[axis] !== undefined) {
return props.minDomain[axis];
}
return typeof props.minDomain === "number" || (0, _isDate.default)(props.minDomain) ? props.minDomain : undefined;
}
/**
* Returns a symmetrically padded domain for polar charts
* @param {Array} domain: the original domain
* @param {Array} values: a flat array of values corresponding to either tickValues, or data values
* for a given dimension i.e. only x values.
* @returns {Array} the symmetric domain
*/
function getSymmetricDomain(domain, values) {
const processedData = (0, _sortedUniq.default)(values.sort((a, b) => a - b));
const step = processedData[1] - processedData[0];
return [domain[0], domain[1] + step];
}
/**
* Checks whether a given component can be used to calculate domain
* @param {Component} component: a React component instance
* @returns {Boolean} Returns true if the given component has a role included in the whitelist
*/
function isDomainComponent(component) {
const getRole = child => {
return child && child.type ? child.type.role : "";
};
let role = getRole(component);
if (role === "portal") {
const children = _react.default.Children.toArray(component.props.children);
role = children.length ? getRole(children[0]) : "";
}
const whitelist = ["area", "axis", "bar", "boxplot", "candlestick", "errorbar", "group", "histogram", "line", "pie", "scatter", "stack", "voronoi"];
return whitelist.includes(role);
}