@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/
111 lines (110 loc) • 3.79 kB
JavaScript
import { dodgeOffsets } from "./dodge-internal.js";
import { resolveDotLayout } from "./dot-layout.js";
import { isChartValue } from "./mark.js";
function createDotLayout(options) {
if (options.axis !== "x" && options.axis !== "y") {
throw new TypeError(
`createDotLayout: unknown axis "${String(options.axis)}"`
);
}
if (typeof options.resolve !== "function") {
throw new TypeError("createDotLayout: resolve must be a function");
}
if (!isChartValue(options.anchor)) {
throw new TypeError(
"createDotLayout: anchor must be a string, finite number, or valid Date"
);
}
return {
axis: options.axis,
anchor: options.anchor,
[resolveDotLayout]: options.resolve
};
}
function dodgeX(options = {}) {
const anchor = options.anchor ?? "left";
const padding = validPadding(options.padding);
const compress = options.fit === "compress";
if (anchor !== "left" && anchor !== "middle" && anchor !== "right") {
throw new TypeError(`dodgeX: unknown anchor "${String(anchor)}"`);
}
return {
axis: "x",
anchor,
[resolveDotLayout]: ({ chart, measuredPositions, radii }) => {
const edgeAnchored = anchor !== "middle";
const offsets = dodgeOffsets(
measuredPositions,
radii,
padding,
edgeAnchored
);
if (compress) compressOffsets(offsets, radii, chart.width, edgeAnchored);
const baseline = anchor === "left" ? chart.x : anchor === "right" ? chart.x + chart.width : chart.x + chart.width / 2;
const direction = anchor === "right" ? -1 : 1;
return offsets.map((offset) => baseline + offset * direction);
}
};
}
function dodgeY(options = {}) {
const anchor = options.anchor ?? "bottom";
const padding = validPadding(options.padding);
const compress = options.fit === "compress";
if (anchor !== "top" && anchor !== "middle" && anchor !== "bottom") {
throw new TypeError(`dodgeY: unknown anchor "${String(anchor)}"`);
}
return {
axis: "y",
anchor,
[resolveDotLayout]: ({ chart, measuredPositions, radii }) => {
const edgeAnchored = anchor !== "middle";
const offsets = dodgeOffsets(
measuredPositions,
radii,
padding,
edgeAnchored
);
if (compress) compressOffsets(offsets, radii, chart.height, edgeAnchored);
const baseline = anchor === "top" ? chart.y : anchor === "bottom" ? chart.y + chart.height : chart.y + chart.height / 2;
const direction = anchor === "bottom" ? -1 : 1;
return offsets.map((offset) => baseline + offset * direction);
}
};
}
function compressOffsets(offsets, radii, size, edgeAnchored) {
if (!offsets.length) return;
let minimum = Infinity;
let maximum = -Infinity;
let radius = 0;
let fits = true;
const start = edgeAnchored ? 0 : -size / 2;
for (let index = 0; index < offsets.length; index++) {
const offset = offsets[index];
const r = radii[index];
minimum = Math.min(minimum, offset);
maximum = Math.max(maximum, offset);
radius = Math.max(radius, r);
if (offset - r < start || offset + r > start + size) fits = false;
}
if (fits) return;
if (radius * 2 > size)
throw new RangeError(
"dodge: compress requires each circle diameter to fit within the plot"
);
const factor = maximum === minimum ? 0 : (size - radius * 2) / (maximum - minimum);
for (let index = 0; index < offsets.length; index++) {
offsets[index] = start + radius + (offsets[index] - minimum) * factor;
}
}
function validPadding(padding) {
const resolved = padding ?? 1;
if (!Number.isFinite(resolved) || resolved < 0) {
throw new TypeError("dodge: padding must be a nonnegative finite number");
}
return resolved;
}
export {
createDotLayout,
dodgeX,
dodgeY
};