<!-- coverage-seen-elsewhere:
  watermark_text -> watermark.md
  crosshair_mode -> styling.md
  background_color -> styling.md
-->

# Time Scale

The time scale is the horizontal axis at the bottom of a chart. TVL exposes its visibility, label density, border, tick formatting, scroll offset, and the underlying time-value model (UTC timestamps vs. business days) through the `tvl.time_scale(...)` object, which you pass to `chart(time_scale=...)`. Additional options (`BusinessDay`, `business_day()`, `is_business_day()`, `is_utc_timestamp()`) are available for when you need to work outside numeric timestamps.

## What are the time-scale options useful for?

- **Trading-day charts**: Skip non-business days so a five-day work week and ten holidays per year stop pushing data off-screen.
- **Aligning multiple charts**: Pin the right edge with `fix_right_edge` so two charts stacked vertically share a consistent time anchor.
- **Live tail UX**: `right_offset` reserves whitespace on the right so the freshest bar isn’t glued to the price scale.
- **Dense time axes**: `tick_mark_max_character_length` and `uniform_distribution` keep labels readable on narrow charts.

## Examples

### Show time and seconds on the axis

The two most common toggles are `time_visible` (show time-of-day on intra-day data) and `seconds_visible` (show seconds when zoomed in tight enough). Set both for high-frequency data.

```python order=chart,values
import deephaven.plot.tradingview_lightweight as tvl

values = tvl.data.values()

chart = tvl.chart(
    tvl.line(values, timestamp="Timestamp", value="Value"),
    time_scale=tvl.time_scale(
        time_visible=True,
        seconds_visible=True,
    ),
)
```

When `time_visible=False`, the axis shows only the date, which suits daily-bar charts.

### Hide the time scale entirely

Set `tvl.time_scale(visible=False)` to hide the bottom axis when you have several stacked panes and only need the axis on the outermost one.

```python order=chart,values
import deephaven.plot.tradingview_lightweight as tvl

values = tvl.data.values()

chart = tvl.chart(
    tvl.line(values, timestamp="Timestamp", value="Value"),
    time_scale=tvl.time_scale(visible=False),
)
```

Combine with `pane_index` (see [multi-pane](multi-pane.md)) to hide the per-pane axis on all but the bottom pane.

### Right-offset whitespace for live charts

`right_offset` adds bars of empty space to the right of the latest data point, keeping the live tip visually separated from the price scale. Use `right_offset_pixels` for a fixed pixel inset instead. All of these live on `tvl.time_scale(...)`.

```python order=chart,values
import deephaven.plot.tradingview_lightweight as tvl

values = tvl.data.values()

chart = tvl.chart(
    tvl.line(values, timestamp="Timestamp", value="Value"),
    time_scale=tvl.time_scale(
        right_offset=12,
        bar_spacing=8,
    ),
)
```

`bar_spacing` (initial pixels per bar) plus `min_bar_spacing` / `max_bar_spacing` (zoom limits) let you set the zoom envelope.

### Pin the visible range edges

`fix_left_edge` and `fix_right_edge` lock the visible range to the data boundaries, so scrolling stops at the first and last bar respectively. `lock_visible_time_range_on_resize` keeps the same logical window when the widget is resized, and `right_bar_stays_on_scroll` keeps the latest bar parked at the right.

```python order=chart,values
import deephaven.plot.tradingview_lightweight as tvl

values = tvl.data.values()

chart = tvl.chart(
    tvl.line(values, timestamp="Timestamp", value="Value"),
    time_scale=tvl.time_scale(
        fix_left_edge=True,
        fix_right_edge=True,
        lock_visible_time_range_on_resize=True,
        right_bar_stays_on_scroll=True,
        shift_visible_range_on_new_bar=False,
    ),
)
```

For live charts you usually want `right_bar_stays_on_scroll=True` and `shift_visible_range_on_new_bar=True` so each new bar slides the viewport.

### Tune tick density

`tick_mark_max_character_length` caps how many characters each tick label can use; the chart will drop ticks until the labels fit. `uniform_distribution` enforces a uniform tick spacing rather than the default “snap to nice intervals” behavior. `minimum_height` reserves vertical space so the time scale doesn’t shrink below a usable height.

```python order=chart,values
import deephaven.plot.tradingview_lightweight as tvl

values = tvl.data.values()

chart = tvl.chart(
    tvl.line(values, timestamp="Timestamp", value="Value"),
    time_scale=tvl.time_scale(
        tick_mark_max_character_length=10,
        uniform_distribution=True,
        minimum_height=32,
        ticks_visible=True,
        allow_bold_labels=True,
    ),
)
```

`allow_bold_labels` lets the chart render bold weight at major boundaries (year, month) for emphasis.

### Style the time-scale border

The border between the time scale and the plot area is its own layer. Toggle visibility with `tvl.time_scale(border_visible=...)` and color with `border_color`.

```python order=chart,values
import deephaven.plot.tradingview_lightweight as tvl

values = tvl.data.values()

chart = tvl.chart(
    tvl.line(values, timestamp="Timestamp", value="Value"),
    time_scale=tvl.time_scale(
        border_visible=True,
        border_color="#888",
    ),
)
```

Hide the border for borderless dashboard tiles.

### Conflate sub-pixel points

`enable_conflation=True` merges data points that fall on the same pixel column, trading a little precision for faster rendering on dense charts. `precompute_conflation_on_init=True` does that work upfront at load; `precompute_conflation_priority` (a `PrecomputeConflationPriority` value) sets how eagerly it runs (`"background"`, `"user-visible"`, `"user-blocking"`).

```python order=chart,values
import deephaven.plot.tradingview_lightweight as tvl

values = tvl.data.values()

chart = tvl.chart(
    tvl.line(values, timestamp="Timestamp", value="Value"),
    time_scale=tvl.time_scale(
        enable_conflation=True,
        precompute_conflation_on_init=True,
        # priority is one of "background", "user-visible", "user-blocking"
        precompute_conflation_priority="background",
    ),
)
```

### Business-day timestamps for trading-hours charts

The `BusinessDay` TypedDict lets you label points without weekends and holidays; the time scale plots business days as equally-spaced ticks regardless of calendar gaps. Use `business_day(year, month, day)` to construct them.

```python
import deephaven.plot.tradingview_lightweight as tvl

# Build a few business-day points
b1 = tvl.business_day(2024, 1, 2)  # Tuesday
b2 = tvl.business_day(2024, 1, 3)  # Wednesday
b3 = tvl.business_day(2024, 1, 4)  # Thursday

print(b1)  # {'year': 2024, 'month': 1, 'day': 2}
```

`BusinessDay` instances are dicts, so they round-trip cleanly through tables and JSON. The companion type-guard helpers, `is_business_day()` and `is_utc_timestamp()`, let you tell business-day points apart from numeric UTC timestamps:

```python
import deephaven.plot.tradingview_lightweight as tvl

bd = tvl.business_day(2024, 1, 2)
ts = 1_704_153_600  # seconds since epoch, UTC

assert tvl.is_business_day(bd)
assert not tvl.is_business_day(ts)
assert tvl.is_utc_timestamp(ts)
assert not tvl.is_utc_timestamp(bd)
```

The two predicates distinguish business-day points from numeric UTC timestamps. The `TickMarkType` Literal alias names the label kinds the time scale renders at each zoom level; TVL picks the right one per tick automatically.

### UTC vs. local timestamps

TVL’s underlying renderer treats numeric time values as **seconds since the Unix epoch, UTC**. Deephaven `Instant` columns are encoded with nanosecond precision; the plugin converts them to UTC seconds when serializing. Use `is_utc_timestamp()` to tell raw numeric times apart from `BusinessDay` dicts when writing helpers that accept either.

```python
import deephaven.plot.tradingview_lightweight as tvl


def label_kind(t):
    if tvl.is_business_day(t):
        return "business_day"
    if tvl.is_utc_timestamp(t):
        return "utc_timestamp"
    return "other"


print(label_kind(tvl.business_day(2024, 1, 2)))  # business_day
print(label_kind(1_704_153_600))  # utc_timestamp
print(label_kind("2024-01-02"))  # other
```

## Time zones and daylight saving

Charts render times in the time zone from your Deephaven **Settings**, falling back to the browser’s zone when unset. The offset is resolved per timestamp, so a series spanning a daylight saving transition keeps correct labels on both sides of it, and day gridlines land on local midnight.

The chart’s underlying coordinate is always UTC. Only the axis ticks and labels are zone-aware, so every row keeps a distinct position even when two instants share the same local wall-clock time. Both instants of an autumn “fall back” hour — for example `05:30 UTC` (01:30 EDT) and `06:30 UTC` (01:30 EST) in `America/New_York` — plot separately.

Changing the Settings time zone re-labels and re-ticks the axis in place; the data and your current zoom are unaffected.

## API Reference

The time scale is configured with a grouped object: `tvl.time_scale(...)`
returns a `TimeScale` that you pass to `time_scale=` on `tvl.chart(...)`.

Create a TimeScale config for tvl.chart(time_scale=...).

**Returns:** `TimeScale` A time-scale config for tvl.chart(time_scale=...).

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "time_scale", "parameters": [{"name": "visible", "type": "bool | None", "description": "Master visibility toggle for the time scale.", "default": "None"}, {"name": "time_visible", "type": "bool | None", "description": "Show the time (not just the date) in labels.", "default": "None"}, {"name": "seconds_visible", "type": "bool | None", "description": "Show seconds in time labels.", "default": "None"}, {"name": "border_visible", "type": "bool | None", "description": "Show the time-scale border.", "default": "None"}, {"name": "border_color", "type": "Literal['gray-50', 'gray-75', 'gray-100', 'gray-200', 'gray-300', 'gray-400', 'gray-500', 'gray-600', 'gray-700', 'gray-800', 'gray-900', 'red-100', 'red-200', 'red-300', 'red-400', 'red-500', 'red-600', 'red-700', 'red-800', 'red-900', 'red-1000', 'red-1100', 'red-1200', 'red-1300', 'red-1400', 'orange-100', 'orange-200', 'orange-300', 'orange-400', 'orange-500', 'orange-600', 'orange-700', 'orange-800', 'orange-900', 'orange-1000', 'orange-1100', 'orange-1200', 'orange-1300', 'orange-1400', 'yellow-100', 'yellow-200', 'yellow-300', 'yellow-400', 'yellow-500', 'yellow-600', 'yellow-700', 'yellow-800', 'yellow-900', 'yellow-1000', 'yellow-1100', 'yellow-1200', 'yellow-1300', 'yellow-1400', 'chartreuse-100', 'chartreuse-200', 'chartreuse-300', 'chartreuse-400', 'chartreuse-500', 'chartreuse-600', 'chartreuse-700', 'chartreuse-800', 'chartreuse-900', 'chartreuse-1000', 'chartreuse-1100', 'chartreuse-1200', 'chartreuse-1300', 'chartreuse-1400', 'celery-100', 'celery-200', 'celery-300', 'celery-400', 'celery-500', 'celery-600', 'celery-700', 'celery-800', 'celery-900', 'celery-1000', 'celery-1100', 'celery-1200', 'celery-1300', 'celery-1400', 'green-100', 'green-200', 'green-300', 'green-400', 'green-500', 'green-600', 'green-700', 'green-800', 'green-900', 'green-1000', 'green-1100', 'green-1200', 'green-1300', 'green-1400', 'seafoam-100', 'seafoam-200', 'seafoam-300', 'seafoam-400', 'seafoam-500', 'seafoam-600', 'seafoam-700', 'seafoam-800', 'seafoam-900', 'seafoam-1000', 'seafoam-1100', 'seafoam-1200', 'seafoam-1300', 'seafoam-1400', 'cyan-100', 'cyan-200', 'cyan-300', 'cyan-400', 'cyan-500', 'cyan-600', 'cyan-700', 'cyan-800', 'cyan-900', 'cyan-1000', 'cyan-1100', 'cyan-1200', 'cyan-1300', 'cyan-1400', 'blue-100', 'blue-200', 'blue-300', 'blue-400', 'blue-500', 'blue-600', 'blue-700', 'blue-800', 'blue-900', 'blue-1000', 'blue-1100', 'blue-1200', 'blue-1300', 'blue-1400', 'indigo-100', 'indigo-200', 'indigo-300', 'indigo-400', 'indigo-500', 'indigo-600', 'indigo-700', 'indigo-800', 'indigo-900', 'indigo-1000', 'indigo-1100', 'indigo-1200', 'indigo-1300', 'indigo-1400', 'purple-100', 'purple-200', 'purple-300', 'purple-400', 'purple-500', 'purple-600', 'purple-700', 'purple-800', 'purple-900', 'purple-1000', 'purple-1100', 'purple-1200', 'purple-1300', 'purple-1400', 'fuchsia-100', 'fuchsia-200', 'fuchsia-300', 'fuchsia-400', 'fuchsia-500', 'fuchsia-600', 'fuchsia-700', 'fuchsia-800', 'fuchsia-900', 'fuchsia-1000', 'fuchsia-1100', 'fuchsia-1200', 'fuchsia-1300', 'fuchsia-1400', 'magenta-100', 'magenta-200', 'magenta-300', 'magenta-400', 'magenta-500', 'magenta-600', 'magenta-700', 'magenta-800', 'magenta-900', 'magenta-1000', 'magenta-1100', 'magenta-1200', 'magenta-1300', 'magenta-1400', 'negative', 'notice', 'positive', 'info', 'accent', 'accent-100', 'accent-200', 'accent-300', 'accent-400', 'accent-500', 'accent-600', 'accent-700', 'accent-800', 'accent-900', 'accent-1000', 'accent-1100', 'accent-1200', 'accent-1300', 'accent-1400', 'bg', 'content-bg', 'subdued-content-bg', 'surface-bg', 'fg'] | str | None", "description": "Border CSS color.", "default": "None"}, {"name": "right_offset", "type": "int | None", "description": "Empty bars kept beyond the rightmost data point.", "default": "None"}, {"name": "right_offset_pixels", "type": "int | None", "description": "Pixel offset of the right edge.", "default": "None"}, {"name": "bar_spacing", "type": "float | None", "description": "Pixels between adjacent bars.", "default": "None"}, {"name": "min_bar_spacing", "type": "float | None", "description": "Minimum bar spacing (zoom-in cap).", "default": "None"}, {"name": "max_bar_spacing", "type": "float | None", "description": "Maximum bar spacing (zoom-out cap).", "default": "None"}, {"name": "fix_left_edge", "type": "bool | None", "description": "Prevent scrolling past the leftmost data point.", "default": "None"}, {"name": "fix_right_edge", "type": "bool | None", "description": "Prevent scrolling past the rightmost data point.", "default": "None"}, {"name": "lock_visible_time_range_on_resize", "type": "bool | None", "description": "Keep the visible range on resize.", "default": "None"}, {"name": "right_bar_stays_on_scroll", "type": "bool | None", "description": "Pin the rightmost bar while scrolling.", "default": "None"}, {"name": "shift_visible_range_on_new_bar", "type": "bool | None", "description": "Auto-scroll when a new bar is added.", "default": "None"}, {"name": "allow_shift_visible_range_on_whitespace_replacement", "type": "bool | None", "description": "Shift when whitespace bars are replaced by real data.", "default": "None"}, {"name": "ticks_visible", "type": "bool | None", "description": "Show tick marks on the time scale.", "default": "None"}, {"name": "tick_mark_max_character_length", "type": "int | None", "description": "Max characters in a tick label before truncation.", "default": "None"}, {"name": "uniform_distribution", "type": "bool | None", "description": "Force uniform bar spacing regardless of timestamp gaps.", "default": "None"}, {"name": "minimum_height", "type": "int | None", "description": "Minimum height of the time-scale area in pixels.", "default": "None"}, {"name": "allow_bold_labels", "type": "bool | None", "description": "Allow bold time labels.", "default": "None"}, {"name": "ignore_whitespace_indices", "type": "bool | None", "description": "Ignore whitespace indices in visible-range math.", "default": "None"}, {"name": "enable_conflation", "type": "bool | None", "description": "Conflate sub-pixel data points for performance.", "default": "None"}, {"name": "conflation_threshold_factor", "type": "float | None", "description": "Conflation sensitivity multiplier.", "default": "None"}, {"name": "precompute_conflation_on_init", "type": "bool | None", "description": "Precompute conflation on chart init.", "default": "None"}, {"name": "precompute_conflation_priority", "type": "Literal['background', 'user-visible', 'user-blocking'] | None", "description": "Scheduling priority for precomputation; see PrecomputeConflationPriority.", "default": "None"}]}} />
Example:

**Returns:** `BusinessDay` A dict &#123;"year": year, "month": month, "day": day&#125; suitable for
use as the time field in TradingView Lightweight Charts data rows.

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "business_day", "parameters": [{"name": "year", "type": "int", "description": "Four-digit year (e.g. 2024)."}, {"name": "month", "type": "int", "description": "Month number, 1-12."}, {"name": "day", "type": "int", "description": "Day of month, 1-31."}]}} />
Example:

**Returns:** `bool` True if time is a dict containing year, month, and
day integer keys; False otherwise.

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "is_business_day", "parameters": [{"name": "time", "type": "Any", "description": "A time value -- either a BusinessDay dict, a numeric UTC timestamp, or an ISO date string."}]}} />
Example:

**Returns:** `bool` True if time is an int or float (but not a bool);
False otherwise.

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "is_utc_timestamp", "parameters": [{"name": "time", "type": "Any", "description": "A time value -- either a BusinessDay dict, a numeric UTC timestamp, or an ISO date string."}]}} />

For the full `tvl.chart` signature, see the [Chart container](chart.md#api-reference) page.
