in packages/charts/src/chart_types/bullet_graph/renderer/canvas/sub_types/horizontal.ts [21:106]
export function horizontalBullet(
ctx: CanvasRenderingContext2D,
dimensions: BulletPanelDimensions,
style: BulletStyle,
backgroundColor: Color,
activeValue?: ActiveValue | null,
) {
ctx.translate(GRAPH_PADDING.left, 0);
const { datum, colorBands, ticks, scale } = dimensions;
const [start, end] = scale.domain() as GenericDomain;
const [min, max] = sortNumbers([start, end]) as ContinuousDomain;
// Color bands
const verticalAlignment = TARGET_SIZE / 2;
colorBands.forEach((band) => {
ctx.fillStyle = band.color;
ctx.fillRect(band.start, verticalAlignment - BULLET_SIZE / 2, band.size, BULLET_SIZE);
});
// Ticks
ctx.beginPath();
ctx.strokeStyle = backgroundColor;
ctx.lineWidth = TICK_WIDTH;
ticks
.filter((tick) => tick > min && tick < max)
.forEach((tick) => {
ctx.moveTo(scale(tick), verticalAlignment - BULLET_SIZE / 2);
ctx.lineTo(scale(tick), verticalAlignment + BULLET_SIZE / 2);
});
ctx.stroke();
// Bar
const confinedValue = clamp(datum.value, min, max);
const adjustedZero = clamp(0, min, max);
ctx.fillStyle = style.barBackground;
ctx.fillRect(
datum.value > 0 ? scale(adjustedZero) : scale(confinedValue),
verticalAlignment - BAR_SIZE / 2,
confinedValue > 0 ? scale(confinedValue) - scale(adjustedZero) : scale(adjustedZero) - scale(confinedValue),
BAR_SIZE,
);
// Target
if (isFiniteNumber(datum.target) && datum.target <= max && datum.target >= min) {
ctx.fillRect(
scale(datum.target) - TARGET_STROKE_WIDTH / 2,
verticalAlignment - TARGET_SIZE / 2,
TARGET_STROKE_WIDTH,
TARGET_SIZE,
);
}
// Zero baseline
if (isBetween(min, max, true)(0)) {
ctx.fillRect(scale(0) - TICK_WIDTH / 2, verticalAlignment - BULLET_SIZE / 2, TICK_WIDTH, BULLET_SIZE);
}
// Active Value
if (activeValue && (datum.syncCursor || !activeValue.external)) {
ctx.fillRect(
activeValue.value - TARGET_STROKE_WIDTH / 2,
verticalAlignment - TARGET_SIZE / 2,
TARGET_STROKE_WIDTH,
TARGET_SIZE,
);
}
// Tick labels
ctx.fillStyle = style.textColor;
ctx.textBaseline = 'top';
ctx.font = cssFontShorthand(TICK_FONT, TICK_FONT_SIZE);
ticks
.filter((tick) => tick >= min && tick <= max)
.forEach((tick, i) => {
const labelText = datum.tickFormatter(tick);
if (i === ticks.length - 1) {
const availableWidth = Math.abs((start > end ? min : max) - (ticks.at(i) ?? NaN));
const { width: labelWidth } = measureText(ctx)(labelText, TICK_FONT, TICK_FONT_SIZE);
ctx.textAlign = labelWidth >= Math.abs(scale(availableWidth) - scale(0)) ? 'end' : 'start';
} else {
ctx.textAlign = 'start';
}
ctx.fillText(labelText, scale(tick), verticalAlignment + TARGET_SIZE / 2 + TICK_LABEL_PADDING);
});
}