recharts
Version:
React charts
220 lines (210 loc) • 8.75 kB
JavaScript
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
import { getValueByDataKey } from '../util/ChartUtils';
/**
* A tagged union describing the status of an item during animation.
*
* - `matched`: item exists in both previous and next data — interpolate between positions
* - `added`: item is new (no previous position) — animate in
* - `removed`: item was in previous data but not in next — animate out
*
* @since 3.9
* @see {@link https://recharts.github.io/en-US/guide/animations Animation guide}
*/
/**
* A function that extracts a key from an animation item for matching purposes.
* Items in the previous and next arrays that return the same key are considered
* the same logical item and will animate between their positions.
*
* @param item The chart item (e.g., a bar rectangle, a line point, a pie sector)
* @param index The index of the item in the array
* @returns A string or number key, or null if the item cannot be matched
*
* @since 3.9
* @see {@link https://recharts.github.io/en-US/guide/animations Animation guide}
*/
/**
* The union of all accepted `animationMatchBy` prop values:
* a built-in sentinel string, or a custom matching function.
*
* @since 3.9
* @see {@link https://recharts.github.io/en-US/guide/animations Animation guide}
*/
/**
* Match animation items by their array index (the default behavior).
*
* Previous items are paired with next items based on their position
* in the array, with proportional stretching when array lengths differ.
* When going from 5 to 15 items, each old point "covers" approximately 3 new points;
* when shrinking, some old points are skipped.
*
* @example
* import { matchByIndex } from 'recharts';
* <Line animationMatchBy={matchByIndex} />
*
* @since 3.9
* @see {@link https://recharts.github.io/en-US/guide/animations Animation guide}
*/
export var matchByIndex = 'index';
/**
* Match animation items sequentially: previous item 0 pairs with next item 0,
* previous item 1 pairs with next item 1, and so on. When the new array is longer,
* the extra items have no match and animate in from their default position.
* When the new array is shorter, the ancient items are simply dropped.
*
* This is useful when new data is appended at the end of the array, and you want
* existing points to stay in place while new points animate in.
*
* @example
* import { matchAppend } from 'recharts';
* <Line animationMatchBy={matchAppend} />
*
* @since 3.9
* @see {@link https://recharts.github.io/en-US/guide/animations Animation guide}
*/
export var matchAppend = 'append';
/**
* Create a matching function that pairs items by a data key from their payload.
*
* Useful for time-series or streaming charts where new data points are added
* to one end and old points are removed from the other. This ensures existing
* points animate smoothly to their new positions instead of shifting by index.
*
* @param dataKey The key to look up in each item's payload (e.g., 'timestamp', 'date', 'id')
* @returns An AnimationMatchBy function that can be passed to the animationMatchBy prop
*
* @example
* import { matchByDataKey } from 'recharts';
* <Line animationMatchBy={matchByDataKey('timestamp')} />
* <Bar animationMatchBy={matchByDataKey('name')} />
* <Pie animationMatchBy={matchByDataKey('id')} />
*
* @since 3.9
* @see {@link https://recharts.github.io/en-US/guide/animations Animation guide}
*/
export function matchByDataKey(dataKey) {
return item => {
if (item.payload == null || typeof item.payload !== 'object') return null;
var value = getValueByDataKey(item.payload, dataKey);
if (value == null) return null;
if (typeof value === 'string' || typeof value === 'number') return value;
return JSON.stringify(value);
};
}
function tagAlignedItems(alignedPrevItems, nextItems) {
var removedPrevItems = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
var tagged = [];
/*
* We put removed points at the start because we assume that a typical chart animates right-to-left
* and the removed points will disappear from the left edge and outside the plot area.
* If you chart behaves differently you may want to customize your matching function.
*/
for (var prev of removedPrevItems) {
tagged.push({
status: 'removed',
prev
});
}
for (var i = 0; i < nextItems.length; i++) {
// This function intentionally pairs by array index. The strategy-specific functions above are
// responsible for producing an alignedPrevItems array whose indices already correspond to nextItems.
var _prev = alignedPrevItems[i];
var next = nextItems[i];
if (_prev != null) {
tagged.push({
status: 'matched',
prev: _prev,
next
});
} else {
tagged.push({
status: 'added',
next
});
}
}
return tagged;
}
function matchByIndexImpl(prevItems, nextItems) {
var factor = prevItems.length / nextItems.length;
var alignedPrevItems = nextItems.map((_, i) => prevItems[Math.floor(i * factor)]);
return tagAlignedItems(alignedPrevItems, nextItems);
}
function matchAppendImpl(prevItems, nextItems) {
var alignedPrevItems = nextItems.map((_, i) => prevItems[i]);
return tagAlignedItems(alignedPrevItems, nextItems);
}
function buildPrevKeyMap(prevItems, matchBy) {
var prevMap = new Map();
for (var i = 0; i < prevItems.length; i++) {
var _item = prevItems[i];
if (_item == null) continue;
var key = matchBy(_item, i);
if (key != null && !prevMap.has(key)) {
prevMap.set(key, _item);
}
}
return prevMap;
}
/**
* Match previous items to next items by key, and include removed items explicitly.
*
* @internal
*/
function matchByKey(prevItems, nextItems, matchBy) {
var prevMap = buildPrevKeyMap(prevItems, matchBy);
// Track which prev keys were matched
var matchedKeys = new Set();
var alignedPrevItems = nextItems.map((next, i) => {
var key = matchBy(next, i);
if (key != null) {
var prev = prevMap.get(key);
if (prev !== undefined) {
matchedKeys.add(key);
return prev;
}
}
return undefined;
});
// Removed = prev items whose keys were not matched to any next item
var removedPrevItems = [];
for (var _ref3 of prevMap) {
var _ref2 = _slicedToArray(_ref3, 2);
var key = _ref2[0];
var _item2 = _ref2[1];
if (!matchedKeys.has(key)) {
removedPrevItems.push(_item2);
}
}
return tagAlignedItems(alignedPrevItems, nextItems, removedPrevItems);
}
/**
* Match previous items to next items using the given matching strategy and return tagged animation items.
*
* On first render, all next items are returned as `{ status: 'added' }`.
* For key-based matching, unmatched previous items are appended as `{ status: 'removed' }`.
*
* @internal
*/
export function matchAnimationItems(prevItems, nextItems, matchBy) {
if (nextItems == null) {
return null;
}
if (prevItems == null) {
return nextItems.map(next => ({
status: 'added',
next
}));
}
if (matchBy === matchByIndex) {
return matchByIndexImpl(prevItems, nextItems);
}
if (matchBy === matchAppend) {
return matchAppendImpl(prevItems, nextItems);
}
return matchByKey(prevItems, nextItems, matchBy);
}