@@ -142,6 +142,10 @@ export type BarCategoryAxis =
142142 | { mode : "category" ; domain : CategoricalDomain }
143143 | { mode : "numeric" ; domain : AxisDomain ; ticks : number [ ] } ;
144144
145+ export type BarValueAxis =
146+ | { mode : "category" ; domain : CategoricalDomain }
147+ | { mode : "numeric" ; domain : AxisDomain ; ticks : number [ ] } ;
148+
145149/**
146150 * Render a numeric date-aware axis along the bottom of the plot. Aliases
147151 * the bar-axis bottom variant so heatmap can share the implementation.
@@ -190,13 +194,11 @@ export interface BarAxesFormatters {
190194export function renderBarAxesChrome (
191195 canvas : Canvas2D ,
192196 catAxis : BarCategoryAxis ,
193- valueDomain : AxisDomain ,
194- valueTicks : number [ ] ,
197+ valueAxis : BarValueAxis ,
195198 layout : PlotLayout ,
196199 theme : Theme ,
197200 dpr : number ,
198- altDomain ?: AxisDomain ,
199- altTicks ?: number [ ] ,
201+ altAxis : BarValueAxis | undefined ,
200202 isHorizontal = false ,
201203 formatters : BarAxesFormatters = { } ,
202204) : void {
@@ -212,7 +214,7 @@ export function renderBarAxesChrome(
212214 ctx . moveTo ( plot . x , plot . y ) ;
213215 ctx . lineTo ( plot . x , plot . y + plot . height ) ;
214216 ctx . lineTo ( plot . x + plot . width , plot . y + plot . height ) ;
215- if ( altDomain ) {
217+ if ( altAxis ) {
216218 if ( isHorizontal ) {
217219 ctx . moveTo ( plot . x , plot . y ) ;
218220 ctx . lineTo ( plot . x + plot . width , plot . y ) ;
@@ -238,31 +240,49 @@ export function renderBarAxesChrome(
238240 ) ;
239241 }
240242
241- drawNumericXAxis (
242- ctx ,
243- layout ,
244- valueDomain ,
245- valueTicks ,
246- "bottom" ,
247- theme ,
248- formatters . value ,
249- ) ;
250- if ( altDomain && altTicks ) {
251- const origMin = layout . paddedXMin ;
252- const origMax = layout . paddedXMax ;
253- layout . paddedXMin = altDomain . min ;
254- layout . paddedXMax = altDomain . max ;
243+ if ( valueAxis . mode === "category" ) {
244+ // Categorical value axis on the bottom: reuse the X
245+ // categorical painter. Slot indices on the layout's X
246+ // domain already place each category at its slot pixel.
247+ renderCategoricalXTicks ( ctx , layout , valueAxis . domain , theme ) ;
248+ } else {
255249 drawNumericXAxis (
256250 ctx ,
257251 layout ,
258- altDomain ,
259- altTicks ,
260- "top " ,
252+ valueAxis . domain ,
253+ valueAxis . ticks ,
254+ "bottom " ,
261255 theme ,
262- formatters . alt ,
256+ formatters . value ,
263257 ) ;
264- layout . paddedXMin = origMin ;
265- layout . paddedXMax = origMax ;
258+ }
259+
260+ if ( altAxis ) {
261+ // Alt-axis painter expects the layout's X domain to match
262+ // the alt domain — temporarily swap in `altDomain.min/max`
263+ // for the duration of the call. Categorical alt has no
264+ // top-side painter; render with the bottom-side painter
265+ // (visual overlap with the primary side — documented
266+ // limitation; user-pinned categorical alt is rare).
267+ if ( altAxis . mode === "category" ) {
268+ renderCategoricalXTicks ( ctx , layout , altAxis . domain , theme ) ;
269+ } else {
270+ const origMin = layout . paddedXMin ;
271+ const origMax = layout . paddedXMax ;
272+ layout . paddedXMin = altAxis . domain . min ;
273+ layout . paddedXMax = altAxis . domain . max ;
274+ drawNumericXAxis (
275+ ctx ,
276+ layout ,
277+ altAxis . domain ,
278+ altAxis . ticks ,
279+ "top" ,
280+ theme ,
281+ formatters . alt ,
282+ ) ;
283+ layout . paddedXMin = origMin ;
284+ layout . paddedXMax = origMax ;
285+ }
266286 }
267287 } else {
268288 if ( catAxis . mode === "category" ) {
@@ -278,31 +298,40 @@ export function renderBarAxesChrome(
278298 ) ;
279299 }
280300
281- drawYAxis (
282- ctx ,
283- layout ,
284- valueDomain ,
285- valueTicks ,
286- "left" ,
287- theme ,
288- formatters . value ,
289- ) ;
290- if ( altDomain && altTicks ) {
291- const origMin = layout . paddedYMin ;
292- const origMax = layout . paddedYMax ;
293- layout . paddedYMin = altDomain . min ;
294- layout . paddedYMax = altDomain . max ;
301+ if ( valueAxis . mode === "category" ) {
302+ renderCategoricalYTicks ( ctx , layout , valueAxis . domain , theme ) ;
303+ } else {
295304 drawYAxis (
296305 ctx ,
297306 layout ,
298- altDomain ,
299- altTicks ,
300- "right " ,
307+ valueAxis . domain ,
308+ valueAxis . ticks ,
309+ "left " ,
301310 theme ,
302- formatters . alt ,
311+ formatters . value ,
303312 ) ;
304- layout . paddedYMin = origMin ;
305- layout . paddedYMax = origMax ;
313+ }
314+
315+ if ( altAxis ) {
316+ if ( altAxis . mode === "category" ) {
317+ renderCategoricalYTicks ( ctx , layout , altAxis . domain , theme ) ;
318+ } else {
319+ const origMin = layout . paddedYMin ;
320+ const origMax = layout . paddedYMax ;
321+ layout . paddedYMin = altAxis . domain . min ;
322+ layout . paddedYMax = altAxis . domain . max ;
323+ drawYAxis (
324+ ctx ,
325+ layout ,
326+ altAxis . domain ,
327+ altAxis . ticks ,
328+ "right" ,
329+ theme ,
330+ formatters . alt ,
331+ ) ;
332+ layout . paddedYMin = origMin ;
333+ layout . paddedYMax = origMax ;
334+ }
306335 }
307336 }
308337}
0 commit comments