strip more seamless and margin improvements
This commit is contained in:
@@ -152,6 +152,10 @@
|
||||
group?: string;
|
||||
/** Fixed left-axis minimum (otherwise derived from data, including 0) */
|
||||
yMin?: number;
|
||||
/** Minimum breathing room (axis units) above the left-axis data range. */
|
||||
yPadTop?: number;
|
||||
/** Minimum breathing room (axis units) below the left-axis data range. */
|
||||
yPadBottom?: number;
|
||||
/** Fixed left-axis maximum */
|
||||
yMax?: number;
|
||||
/** Force the derived left axis to include zero (default true) */
|
||||
@@ -196,6 +200,8 @@
|
||||
group,
|
||||
yMin,
|
||||
yMax,
|
||||
yPadTop,
|
||||
yPadBottom,
|
||||
zeroBaseLeft = true,
|
||||
yMinRight,
|
||||
yMaxRight,
|
||||
@@ -302,9 +308,11 @@
|
||||
// and the right-axis labels are drawn overlaid on top instead (see below).
|
||||
let padRight = $derived(isNarrow ? 6 : hasRightAxis || reserveRightAxis ? 56 : 20);
|
||||
// Icon rows across the top: pictograms and/or wind arrows. reserveTopRows keeps
|
||||
// a stacked row of charts the same height even if some have fewer icon rows.
|
||||
// a stacked row of charts the same height even if some have fewer icon rows —
|
||||
// but only on wide screens: on mobile that uniform band wastes precious
|
||||
// vertical space, so each chart reserves just its own rows there.
|
||||
let ownIconRows = $derived((pictograms.length > 0 ? 1 : 0) + (windArrows.length > 0 ? 1 : 0));
|
||||
let iconRows = $derived(Math.max(ownIconRows, reserveTopRows));
|
||||
let iconRows = $derived(isNarrow ? ownIconRows : Math.max(ownIconRows, reserveTopRows));
|
||||
// tighter top/bottom gutters on mobile so charts don't waste vertical space
|
||||
const iconRowH = $derived(isNarrow ? 30 : ICON_ROW_H);
|
||||
let padTop = $derived((title ? (subtitle ? 66 : 46) : isNarrow ? 14 : 28) + iconRows * iconRowH);
|
||||
@@ -353,16 +361,38 @@
|
||||
return [lo, hi];
|
||||
}
|
||||
|
||||
function buildScale(lo: number, hi: number, loFixed: boolean, hiFixed: boolean): Scale {
|
||||
function buildScale(
|
||||
lo: number,
|
||||
hi: number,
|
||||
loFixed: boolean,
|
||||
hiFixed: boolean,
|
||||
halfStepBounds = false
|
||||
): Scale {
|
||||
const step = niceNum(niceNum(Math.max(hi - lo, 1e-9), false) / 4, true);
|
||||
const min = loFixed ? lo : Math.floor(lo / step) * step;
|
||||
const max = hiFixed ? hi : Math.ceil(hi / step) * step;
|
||||
// Padded axes (e.g. temperature) may end on HALF steps — 5° when ticks
|
||||
// are every 10° — so the requested margin isn't inflated to a whole step.
|
||||
// Tick drawing starts at the first full-step multiple, so a half-step
|
||||
// bound gets no label or gridline of its own.
|
||||
const snap = halfStepBounds ? step / 2 : step;
|
||||
const min = loFixed ? lo : Math.floor(lo / snap) * snap;
|
||||
const max = hiFixed ? hi : Math.ceil(hi / snap) * snap;
|
||||
return { min, max: max > min ? max : min + step, step };
|
||||
}
|
||||
|
||||
/** First tick at or above the scale minimum (bounds may sit on half steps). */
|
||||
function firstTick(scale: Scale): number {
|
||||
return Math.ceil((scale.min - 1e-9) / scale.step) * scale.step;
|
||||
}
|
||||
|
||||
let leftScale = $derived.by((): Scale => {
|
||||
const [dLo, dHi] = dataExtent('left', zeroBaseLeft);
|
||||
return buildScale(yMin ?? dLo, yMax ?? dHi, yMin !== undefined, yMax !== undefined);
|
||||
let [dLo, dHi] = dataExtent('left', zeroBaseLeft);
|
||||
// requested breathing room around the data (e.g. temperature): at least
|
||||
// the given units, growing with wide ranges so it stays proportionate
|
||||
const span = dHi - dLo;
|
||||
const padded = yPadTop != null || yPadBottom != null;
|
||||
if (yMax === undefined && yPadTop) dHi += Math.max(yPadTop, span * 0.08);
|
||||
if (yMin === undefined && yPadBottom) dLo -= Math.max(yPadBottom, span * 0.12);
|
||||
return buildScale(yMin ?? dLo, yMax ?? dHi, yMin !== undefined, yMax !== undefined, padded);
|
||||
});
|
||||
|
||||
let rightScale = $derived.by((): Scale => {
|
||||
@@ -839,7 +869,11 @@
|
||||
ctx.font = font;
|
||||
ctx.textAlign = 'right';
|
||||
ctx.textBaseline = 'middle';
|
||||
for (let v = leftScale.min; v <= leftScale.max + leftScale.step / 2; v += leftScale.step) {
|
||||
for (
|
||||
let v = firstTick(leftScale);
|
||||
v <= leftScale.max + leftScale.step / 2;
|
||||
v += leftScale.step
|
||||
) {
|
||||
const y = yPix(v, 'left');
|
||||
ctx.strokeStyle = gridColor;
|
||||
ctx.lineWidth = 1;
|
||||
@@ -858,7 +892,7 @@
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillStyle = textColor;
|
||||
for (
|
||||
let v = rightScale.min;
|
||||
let v = firstTick(rightScale);
|
||||
v <= rightScale.max + rightScale.step / 2;
|
||||
v += rightScale.step
|
||||
) {
|
||||
@@ -1153,7 +1187,7 @@
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineJoin = 'round';
|
||||
for (
|
||||
let v = rightScale.min;
|
||||
let v = firstTick(rightScale);
|
||||
v <= rightScale.max + rightScale.step / 2;
|
||||
v += rightScale.step
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user