Lots of frontend changes
This commit is contained in:
parent
ec29631c44
commit
555ba7cf53
38 changed files with 1508 additions and 648 deletions
|
|
@ -11,18 +11,37 @@ function downsampleBars(counts: number[], targetBars: number): number[] {
|
|||
return bars;
|
||||
}
|
||||
|
||||
function pickTicks(min: number, max: number, count: number): number[] {
|
||||
if (max <= min) return [min];
|
||||
const range = max - min;
|
||||
const rawStep = range / (count - 1);
|
||||
const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
||||
const nice = [1, 2, 2.5, 3, 4, 5, 10].find((n) => n * magnitude >= rawStep) ?? 10;
|
||||
const step = nice * magnitude;
|
||||
const start = Math.ceil(min / step) * step;
|
||||
const ticks: number[] = [];
|
||||
for (let v = start; v <= max + step * 0.01; v += step) {
|
||||
ticks.push(v);
|
||||
}
|
||||
// Ensure at least min and max are represented
|
||||
if (ticks.length === 0) return [min, max];
|
||||
return ticks;
|
||||
}
|
||||
|
||||
export function DualHistogram({
|
||||
localCounts,
|
||||
globalCounts,
|
||||
min,
|
||||
max,
|
||||
p1,
|
||||
p99,
|
||||
globalMean,
|
||||
formatLabel,
|
||||
}: {
|
||||
localCounts: number[];
|
||||
globalCounts: number[];
|
||||
min: number;
|
||||
max: number;
|
||||
p1: number;
|
||||
p99: number;
|
||||
globalMean?: number;
|
||||
formatLabel?: (value: number) => string;
|
||||
}) {
|
||||
const targetBars = 25;
|
||||
const localBars = downsampleBars(localCounts, targetBars);
|
||||
|
|
@ -32,7 +51,37 @@ export function DualHistogram({
|
|||
const localMax = Math.max(...localBars, 1);
|
||||
const globalMax = Math.max(...globalBars, 1);
|
||||
|
||||
const meanFraction = globalMean != null && max > min ? (globalMean - min) / (max - min) : null;
|
||||
const fmt = formatLabel ?? ((v: number) => (Number.isInteger(v) ? v.toLocaleString() : v.toFixed(1)));
|
||||
|
||||
// Compute center value for each bar.
|
||||
// Bar 0 = low outlier, bars 1..n-2 = middle (p1 to p99), bar n-1 = high outlier.
|
||||
const middleBins = Math.max(barCount - 2, 0);
|
||||
const middleWidth = middleBins > 0 && p99 > p1 ? (p99 - p1) / middleBins : 0;
|
||||
const barCenters: number[] = Array.from({ length: barCount }, (_, i) => {
|
||||
if (i === 0) return p1; // outlier bin, label as p1
|
||||
if (i === barCount - 1) return p99; // outlier bin, label as p99
|
||||
return p1 + (i - 1 + 0.5) * middleWidth;
|
||||
});
|
||||
|
||||
// Pick nice tick values and assign each to the nearest bar
|
||||
const ticks = p99 > p1 ? pickTicks(p1, p99, 6) : [];
|
||||
const tickBars = new Map<number, string>(); // bar index → label
|
||||
for (const v of ticks) {
|
||||
let bestBar = 1;
|
||||
let bestDist = Infinity;
|
||||
for (let i = 1; i < barCount - 1; i++) {
|
||||
const dist = Math.abs(barCenters[i] - v);
|
||||
if (dist < bestDist) { bestDist = dist; bestBar = i; }
|
||||
}
|
||||
if (!tickBars.has(bestBar)) tickBars.set(bestBar, fmt(v));
|
||||
}
|
||||
|
||||
// Mean line: position as fraction across the bar area
|
||||
const meanFrac = globalMean != null && p99 > p1 ? (globalMean - p1) / (p99 - p1) : null;
|
||||
// Account for outlier bins: middle region spans bars 1..n-2
|
||||
const meanPct = meanFrac != null
|
||||
? ((1 + meanFrac * middleBins) / barCount) * 100
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="mt-1">
|
||||
|
|
@ -56,13 +105,26 @@ export function DualHistogram({
|
|||
</div>
|
||||
);
|
||||
})}
|
||||
{meanFraction != null && meanFraction >= 0 && meanFraction <= 1 && (
|
||||
{meanPct != null && meanPct >= 0 && meanPct <= 100 && (
|
||||
<div
|
||||
className="absolute bottom-0 top-0 w-px border-l border-dashed border-warm-400 dark:border-warm-500"
|
||||
style={{ left: `${meanFraction * 100}%` }}
|
||||
style={{ left: `${meanPct}%` }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{tickBars.size > 0 && (
|
||||
<div className="flex gap-px mt-0.5">
|
||||
{Array.from({ length: barCount }).map((_, index) => (
|
||||
<div key={index} className="flex-1 min-w-[2px] text-center">
|
||||
{tickBars.has(index) && (
|
||||
<span className="text-[9px] leading-none text-warm-400 dark:text-warm-500">
|
||||
{tickBars.get(index)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue