Skip to content

fix: honor timeUnitBandPosition for tick/rect marks with explicit size - #9837

Closed
yhoonkim wants to merge 2 commits into
vega:mainfrom
yhoonkim:fix/tick-timeunit-band-position-with-size
Closed

yhoonkim wants to merge 2 commits into
vega:mainfrom
yhoonkim:fix/tick-timeunit-band-position-with-size

Conversation

@yhoonkim

@yhoonkim yhoonkim commented Apr 21, 2026 •

Copy link
Copy Markdown
Contributor

PR Description

Fixes #9836.

When a tick (or rect-based) mark has an explicit size and its x/y
encoding uses a timeUnit field, config.<mark>.timeUnitBandPosition β€”
and bandPosition set on the field β€” were silently ignored. The tick was
always centered at the bin midpoint, which misaligned it with a bar on
the same encoding when e.g. timeUnitBandPosition: 0 was configured.

Root cause

rectPosition (in src/compile/mark/encode/position-rect.ts) dispatches
to two branches:

  • rectBinPosition β€” honors timeUnitBandPosition via getBandPosition,
    and routes through the *_offsetted_rect_start/end fields.
  • positionAndSize β€” used when size is set; hardcoded bandPosition to
    0.5 for the center ref (xc/yc) and never consulted
    fieldDef.timeUnit or getBandPosition({fieldDef, ...}).

So the second path produced xc: scale("x", 0.5 * datum["date"] + 0.5 * datum["date_end"]) regardless of the configured band position.

Fix

In positionAndSize, when the channel has a timeUnit field and no
secondary channel def, derive bandPosition via the same
getBandPosition({fieldDef, markDef, config}) helper used by
rectBinPosition, and pass it through to midPointRefWithPositionInvalidTest.

With bandPosition: 0, interpolatedSignalRef short-circuits to
{scale: 'x', field: <startField>}, placing the tick's center on the bin
start β€” matching the bar's center under the same config.

Verification

  • Added unit test in test/compile/mark/encode/position-rect.test.ts
    covering tick + explicit size + timeUnitBandPosition: 0.
  • End-to-end on the issue's repro, tick now emits
    xc: {scale: "x", field: "date"}, aligned with the bar.
  • Full unit suite passes locally (6480 tests).

Checklist

  • This PR is atomic (i.e., it fixes one issue at a time).
  • The title is a concise semantic commit message.
  • npm test runs successfully.
  • For new features:
    • Has unit tests.
    • Has documentation under site/docs/ + examples. (bug fix, no docs needed)

@yhoonkim
yhoonkim requested a review from a team as a code owner April 21, 2026 22:23
@yhoonkim
yhoonkim force-pushed the fix/tick-timeunit-band-position-with-size branch from 78f2042 to 8988779 Compare April 21, 2026 22:42
@yhoonkim
yhoonkim requested a review from kanitw April 21, 2026 22:44
@joelostblom

Copy link
Copy Markdown
Contributor

Great to see this getting fixed, thank you! There is a somewhat related PR in #9827 which tries to address the fact that bandSize and size both exist for ticks. Would one of you have time to take a look and weigh in on if the fix is to expose bandSize as that PR suggests, or remove it from the docs as per my comment there?

@yhoonkim
yhoonkim force-pushed the fix/tick-timeunit-band-position-with-size branch from 8988779 to 9227650 Compare April 22, 2026 15:59
…size`

In `positionAndSize`, the center ref used a hardcoded `bandPosition` of 0.5,
so `config.tick.timeUnitBandPosition` (and `bandPosition` on a timeUnit
field) was silently ignored whenever an explicit `size` forced the
tick/rect code path. This misaligned the tick from a bar sharing the same
`timeUnitBandPosition` config.

Derive `bandPosition` via `getBandPosition({fieldDef, markDef, config})`
when the field has a `timeUnit` and no secondary channel def, matching
the behavior of `rectBinPosition`.

Fixes vega#9836
@yhoonkim
yhoonkim force-pushed the fix/tick-timeunit-band-position-with-size branch from 9227650 to d8656c6 Compare April 22, 2026 17:31
@yhoonkim

Copy link
Copy Markdown
Contributor Author

Superseded by #9838 β€” same commits, but the branch lives in vega/vega-lite directly now. Thanks for the review @kanitw!

@yhoonkim yhoonkim closed this Apr 22, 2026
kanitw pushed a commit that referenced this pull request Apr 23, 2026
…size` (#9838)

## PR Description

Fixes #9836.

> Re-opened from #9837 with the branch pushed directly to this repo so
CI
> has full access. Previously reviewed and approved by @kanitw in #9837;
> review feedback (extract `bandPosition` to a local const with `??`) is
> already folded in, along with added tests that cover the three
> branches flagged by codecov's patch check.

When a `tick` (or rect-based) mark has an explicit `size` and its
`x`/`y`
encoding uses a `timeUnit` field, `config.<mark>.timeUnitBandPosition` β€”
and `bandPosition` set on the field β€” were silently ignored. The tick
was
always centered at the bin midpoint, which misaligned it with a `bar` on
the same encoding when e.g. `timeUnitBandPosition: 0` was configured.

### Root cause

`rectPosition` (in `src/compile/mark/encode/position-rect.ts`)
dispatches
to two branches:

- `rectBinPosition` β€” honors `timeUnitBandPosition` via
`getBandPosition`,
  and routes through the `*_offsetted_rect_start/end` fields.
- `positionAndSize` β€” used when `size` is set; hardcoded `bandPosition`
to
  `0.5` for the center ref (`xc`/`yc`) and never consulted
  `fieldDef.timeUnit` or `getBandPosition({fieldDef, ...})`.

So the second path produced `xc: scale("x", 0.5 * datum["date"] + 0.5 *
datum["date_end"])` regardless of the configured band position.

### Fix

In `positionAndSize`, when the channel has a `timeUnit` field and no
secondary channel def, derive `bandPosition` via the same
`getBandPosition({fieldDef, markDef, config})` helper used by
`rectBinPosition`, and pass it through to
`midPointRefWithPositionInvalidTest`.

With `bandPosition: 0`, `interpolatedSignalRef` short-circuits to
`{scale: 'x', field: <startField>}`, placing the tick's center on the
bin
start β€” matching the bar's center under the same config.

### Verification

- New example `layer_bar_tick_binned_time_band_position.vl.json` β€” first
  commit adds it with the broken rendering, second commit applies the
  fix and updates the compiled output so the diff shows the visual
  improvement.
- Unit tests in `test/compile/mark/encode/position-rect.test.ts` cover
  the fix plus the three fallback branches in the refactored expression
  (for patch coverage).
- Full unit suite passes locally.

<details>
  <summary><h2>Checklist</h2></summary>

- [x] This PR is atomic (i.e., it fixes one issue at a time).
- [x] The title is a concise [semantic commit
message](https://www.conventionalcommits.org/).
- [x] `npm test` runs successfully.
- For new features:
  - [x] Has unit tests.
- [x] Has documentation under `site/docs/` + examples. _(bug fix, no
docs needed)_
</details>

---------

Co-authored-by: GitHub Actions Bot <vega-actions-bot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tick mark with explicit size ignores timeUnitBandPosition (misaligns with bar)

3 participants