@tanstack/charts
Version:
A chart grammar for TypeScript and JavaScript. Marks consume your data directly, channels describe visual encodings, and the engine compiles them into a renderer-neutral keyed scene. TanStack's compact scales cover common numeric and categorical mappings.
168 lines (167 loc) • 5.81 kB
JavaScript
import {
channelValues,
createMark,
inferredKeyValues,
isChartKey,
isChartValue,
isFiniteNumber,
markStates,
visualValue
} from "./mark.js";
import { valueKey } from "./scales.js";
import { stackValues } from "./stack-internal.js";
function areaY(source, options = {}) {
const data = Array.isArray(source) ? source : Array.from(source);
return createMark(({ markIndex }) => {
const id = options.id ?? `area-y-${markIndex}`;
const xValues = channelValues(data, options.x, (_datum, { index }) => index);
const rawY = options.y ?? options.y2;
const rawYValues = typeof rawY === "number" ? data.map(() => rawY) : channelValues(
data,
rawY,
(datum) => typeof datum === "number" ? datum : void 0
);
const zValues = channelValues(data, options.z, () => null);
const colorValues = options.color === void 0 ? zValues : channelValues(data, options.color, () => null);
const groupValues = options.z === void 0 && options.color !== void 0 ? colorValues : zValues;
const explicitExtent = options.y1 !== void 0 || options.y2 !== void 0;
if (explicitExtent && options.layout) {
throw new TypeError(
"An area with explicit y1 or y2 endpoints cannot also configure a stack layout"
);
}
const stacked = explicitExtent ? void 0 : stackValues(xValues, rawYValues, groupValues, options.layout);
const y1Values = explicitExtent ? typeof options.y1 === "number" ? data.map(() => options.y1) : channelValues(data, options.y1, () => 0) : stacked.starts;
const y2Values = explicitExtent ? typeof options.y2 === "number" ? data.map(() => options.y2) : channelValues(data, options.y2 ?? options.y, () => void 0) : stacked.ends;
const keys = inferredKeyValues(data, options.key, {
groups: groupValues,
candidates: [xValues],
markId: id,
warningIdentity: options
});
const groups = /* @__PURE__ */ new Map();
groupValues.forEach((value, index) => {
const key = valueKey(value ?? null);
const group = groups.get(key);
if (group) group.push(index);
else groups.set(key, [index]);
});
return {
id,
states: markStates(data, options.states),
seriesFromColor: options.z === void 0 && options.color !== void 0,
channels: {
x: { scale: "x", values: xValues.filter(isChartValue) },
y: {
scale: "y",
values: [
...y2Values.filter(isFiniteNumber),
...y1Values.filter(isFiniteNumber)
],
includeZero: options.y1 === void 0
},
color: {
scale: "color",
values: colorValues.filter(isChartKey)
}
},
render: ({ scales, color: resolveColor }) => {
const nodes = [];
for (const [groupKey, indices] of groups) {
const firstIndex = indices[0];
if (firstIndex === void 0) continue;
const group = groupValues[firstIndex] ?? null;
const datum = data[firstIndex];
const resolvedColor = resolveColor(colorValues[firstIndex] ?? null);
const fill = visualValue(
options.fill,
datum,
firstIndex,
data,
resolvedColor
);
const stroke = options.stroke === void 0 ? void 0 : visualValue(
options.stroke,
datum,
firstIndex,
data,
resolvedColor
);
let top = [];
let bottom = [];
let segmentPoints = [];
let segmentIndex = 0;
const flush = () => {
if (!top.length) return;
const lower = [...bottom].reverse();
const path = options.curve?.area(top, bottom);
nodes.push({
kind: "area",
key: `${id}:${groupKey}:segment:${segmentIndex}`,
points: [...top, ...lower],
path,
interaction: { points: segmentPoints, affinity: "x" },
style: {
fill,
fillOpacity: options.fillOpacity ?? 0.2,
stroke,
strokeWidth: options.strokeWidth
}
});
top = [];
bottom = [];
segmentPoints = [];
segmentIndex += 1;
};
for (const datumIndex of indices) {
const xValue = xValues[datumIndex];
const yValue = rawYValues[datumIndex];
const y1Value = y1Values[datumIndex];
const y2Value = y2Values[datumIndex];
if (!isChartValue(xValue) || !isFiniteNumber(yValue) || !isFiniteNumber(y1Value) || !isFiniteNumber(y2Value)) {
flush();
continue;
}
const x = scales.x.map(xValue);
const y = scales.y.map(y2Value);
top.push([x, y]);
bottom.push([x, scales.y.map(y1Value)]);
const key = `${id}:${groupKey}:${valueKey(keys[datumIndex])}`;
const point = {
key,
markId: id,
group,
groupLabel: group == null ? id : String(group),
datum: data[datumIndex],
datumIndex,
xValue,
yValue,
y1Value,
y2Value,
yInterval: "difference",
x,
y,
color: fill
};
segmentPoints.push(point);
}
flush();
}
return {
nodes: [
{
kind: "group",
key: id,
className: "ts-chart__area",
ariaHidden: true,
children: nodes
}
]
};
}
};
}, options.motion);
}
export {
areaY
};