@tanstack/charts
Version:
<div align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/api/readme/charts.png?theme=dark" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/
159 lines (158 loc) • 5.62 kB
JavaScript
import { stackOrderInsideOut } from "d3-shape";
import { valueKey } from "./scales.js";
function stackOuterEnds(positions, values, series, starts, ends, options = {}, fallbackSeries = "value") {
const input = stackEndInput(
positions,
values,
series,
starts,
ends,
fallbackSeries
);
const orderedSeries = resolveSeriesOrder(input, options.order);
if (options.reverse) orderedSeries.reverse();
const seriesRank = new Map(
orderedSeries.map((value, index) => [valueKey(value), index])
);
const orderedInput = [...input].sort(
(left, right) => seriesRank.get(valueKey(left.series)) - seriesRank.get(valueKey(right.series))
);
const outerEnds = Array.from({
length: positions.length
});
const paintGroups = groupStackEnds(input);
const orderedGroups = groupStackEnds(orderedInput);
const diverging = options.offset === void 0 || options.offset === "diverging";
for (const [position, rows] of orderedGroups) {
const paintRows = paintGroups.get(position);
if (diverging) {
const minimum = Math.min(...rows.flatMap((row) => [row.start, row.end]));
const maximum = Math.max(...rows.flatMap((row) => [row.start, row.end]));
if (maximum > 0) markExtremeEnd(rows, paintRows, "max", outerEnds);
if (minimum < 0) markExtremeEnd(rows, paintRows, "min", outerEnds);
} else {
const baseline = rows[0].start;
const terminal = rows.at(-1).end;
let extreme;
if (terminal < baseline) extreme = "min";
else if (terminal > baseline) extreme = "max";
else {
const minimum = Math.min(...rows.flatMap((row) => [row.start, row.end]));
const maximum = Math.max(...rows.flatMap((row) => [row.start, row.end]));
extreme = baseline - minimum > maximum - baseline ? "min" : "max";
}
markExtremeEnd(rows, paintRows, extreme, outerEnds);
}
}
return outerEnds;
}
function groupStackEnds(input) {
const groups = /* @__PURE__ */ new Map();
for (const row of input) {
if (row.value === 0 || row.start === row.end) continue;
const position = valueKey(row.position);
const group = groups.get(position);
if (group) group.push(row);
else groups.set(position, [row]);
}
return groups;
}
function markExtremeEnd(geometryRows, paintRows, extreme, outerEnds) {
if (geometryRows.length === 0) return;
const target = Math[extreme](
...geometryRows.flatMap((row) => [row.start, row.end])
);
for (const row of paintRows) {
const start = row.start === target;
const end = row.end === target;
if (!start && !end) continue;
const existing = outerEnds[row.index];
outerEnds[row.index] = {
start: existing?.start === true || start,
end: existing?.end === true || end
};
}
}
function stackEndInput(positions, values, series, starts, ends, fallbackSeries) {
const input = [];
for (let index = 0; index < positions.length; index += 1) {
const position = positions[index];
const value = values[index];
const start = starts[index];
const end = ends[index];
if (!isChartValue(position) || !isFiniteNumber(value) || !isFiniteNumber(start) || !isFiniteNumber(end))
continue;
const seriesValue = series[index];
input.push({
index,
position,
value,
start,
end,
series: isChartKey(seriesValue) ? seriesValue : fallbackSeries === "index" ? index : "value"
});
}
return input;
}
function resolveSeriesOrder(input, order) {
const firstSeen = [];
const seen = /* @__PURE__ */ new Set();
for (const row of input) {
const identity = valueKey(row.series);
if (seen.has(identity)) continue;
seen.add(identity);
firstSeen.push(row.series);
}
if (Array.isArray(order)) {
const explicit = [...order];
const explicitKeys = new Set(explicit.map(valueKey));
return [
...explicit,
...firstSeen.filter((value) => !explicitKeys.has(valueKey(value)))
];
}
if (order === "inside-out") {
const positions = [];
const positionIndex = /* @__PURE__ */ new Map();
for (const row of input) {
const identity = valueKey(row.position);
if (positionIndex.has(identity)) continue;
positionIndex.set(identity, positions.length);
positions.push(row.position);
}
const seriesValues = firstSeen.map((seriesValue) => {
const identity = valueKey(seriesValue);
const output = positions.map(() => [0, 0]);
for (const row of input) {
if (valueKey(row.series) !== identity) continue;
output[positionIndex.get(valueKey(row.position))][1] = row.value;
}
return output;
});
return stackOrderInsideOut(
seriesValues
).map((index) => firstSeen[index]);
}
if (order !== "ascending" && order !== "descending") return firstSeen;
const totals = new Map(firstSeen.map((value) => [valueKey(value), 0]));
for (const row of input) {
const key = valueKey(row.series);
totals.set(key, (totals.get(key) ?? 0) + Math.abs(row.value));
}
return firstSeen.sort((left, right) => {
const difference = (totals.get(valueKey(left)) ?? 0) - (totals.get(valueKey(right)) ?? 0);
return order === "ascending" ? difference : -difference;
});
}
function isChartKey(value) {
return typeof value === "string" || typeof value === "number";
}
function isChartValue(value) {
return typeof value === "string" || isFiniteNumber(value) || value instanceof Date && Number.isFinite(value.getTime());
}
function isFiniteNumber(value) {
return typeof value === "number" && Number.isFinite(value);
}
export {
stackOuterEnds
};