<!-- coverage-seen-elsewhere:
  markers -> markers.md
  price_lines -> price-lines.md
  marker_spec -> markers.md
  on_press -> events.md
  on_double_press -> events.md
  crosshair_mode -> styling.md
  time_visible -> time-scale.md
  watermark_text -> watermark.md
  last_value_visible -> titles-legends.md
  visible -> titles-legends.md
  price_scale_id -> price-scale.md
  price_format -> price-formats.md
  last_price_line -> price-lines.md
  base_line -> price-scale.md
  price_scale -> price-scale.md
  color_column -> histogram.md
  pane -> multi-pane.md
-->

# Line Chart

A line chart draws a single continuous line through `(timestamp, value)` points to show how one number changes over time. Use it when the value is a continuous quantity (price, yield, latency, count) sampled at ordered timestamps.

Line charts work well with TVL’s `by` argument, which partitions one input table into one line per unique value. That lets you overlay several symbols or strategies on the same axis without manually splitting the table.

## What are line charts useful for?

- **Showing trend over time**: A single line strips away everything but the trajectory, which is what you usually want for a quick “is it going up or down” read.
- **Comparing multiple series**: With `by` you can overlay several groups on the same axis and visually compare their levels and slopes.
- **Highlighting state changes**: With `line_type="with_steps"` you can render a series of discrete state data (regimes, flags, tiers) as a clean staircase.
- **Annotating with markers and price lines**: A thin line leaves room to layer event markers and level lines on top. See [markers](markers.md) and [price-lines](price-lines.md).

## Examples

### A basic line chart

Pass a table plus `timestamp` and `value` column names. With `tvl.data.values()` (columns `Timestamp` and `Value`) the defaults already match, but they are shown here explicitly for clarity.

```python order=line,data
import deephaven.plot.tradingview_lightweight as tvl
data = tvl.data.values()

line = tvl.line(data, timestamp="Timestamp", value="Value")
```

A single line is drawn through 90 daily points.

### Customize color and width

`color` is a theme color or a CSS color of the line; `line_width` is an integer pixel width from 1 to 4 (the LWC default is 3). The `LineWidth` type alias formalizes that range as `Literal[1, 2, 3, 4]`. Bump width up when the chart is going on a big monitor; bump it down when you have many series overlaid.

`color` accepts a Deephaven theme color (e.g. `"positive"`, `"seafoam-800"`, `"accent-300"`), a hex code (`"#1f77b4"`), a named CSS color (`"steelblue"`), or an `rgb()`/`rgba()` string for transparency. Theme colors adapt automatically when the user switches themes; hardcoded values do not.

```python order=line,data
import deephaven.plot.tradingview_lightweight as tvl
data = tvl.data.values()

line = tvl.line(
    data,
    timestamp="Timestamp",
    value="Value",
    color="positive",
    line_width=1,
)
```

The line is drawn in the user’s theme “positive” color (so the same code reads cleanly in light or dark mode) and one pixel wide.

### Every `LineStyle` value

`line_style` accepts five named presets that map to the LWC `LineStyle` enum: `"solid"`, `"dotted"`, `"dashed"`, `"large_dashed"`, and `"sparse_dotted"`. To compare them in one image, give each variant its own vertical offset so the lines stack rather than overlap.

```python order=line_styles
import deephaven.plot.tradingview_lightweight as tvl

base = tvl.data.values()
styles = [
    ("solid", 40.0),
    ("dotted", 20.0),
    ("dashed", 0.0),
    ("large_dashed", -20.0),
    ("sparse_dotted", -40.0),
]


def styled_line(name, offset):
    # Offset each series vertically so the styles don't overlap.
    return tvl.line(
        base.update([f"Offset_{name} = Value + {offset}"]),
        timestamp="Timestamp",
        value=f"Offset_{name}",
        line_style=name,
        title=name,
    )


style_lines = [styled_line(name, offset) for name, offset in styles]
line_styles = tvl.chart(*style_lines)
```

All five styles render on the same chart, vertically offset.

### Every `LineType` value

`line_type` controls how the line is drawn *between* points. The three options are:

- `"simple"`: straight segments (the default).
- `"with_steps"`: horizontal segment then a vertical jump at each new point, useful for state data that hold constant between samples.
- `"curved"`: monotone-cubic interpolation for a smoothed look.

```python order=line_types
import deephaven.plot.tradingview_lightweight as tvl

base = tvl.data.values()
types = [
    ("simple", 20.0),
    ("with_steps", 0.0),
    ("curved", -20.0),
]


def typed_line(name, offset):
    # Offset each series vertically so the line types don't overlap.
    return tvl.line(
        base.update([f"Offset_{name} = Value + {offset}"]),
        timestamp="Timestamp",
        value=f"Offset_{name}",
        line_type=name,
        title=name,
    )


type_lines = [typed_line(name, offset) for name, offset in types]
line_types = tvl.chart(*type_lines)
```

All three types render on the same chart, vertically offset. `with_steps` is the right choice for non-interpolated discrete-state data; `curved` is purely cosmetic.

### One line per group with `by`

Set `by` to a partition column and the chart creates one line per unique value, automatically picking a distinct color for each from the user’s theme palette. New partition keys that show up at runtime (in a ticking table) cause new series to be added on the fly.

```python order=line,data
import deephaven.plot.tradingview_lightweight as tvl
data = tvl.data.stocks()

line = tvl.line(
    data,
    timestamp="Timestamp",
    value="Price",
    by="Sym",
)
```

`tvl.data.stocks()` is a three-symbol (`AAA`, `BBB`, `CCC`) walk with enough independent variance per series that the three lines spread across the chart instead of stacking on top of each other.

### Point markers on every data point

`point_markers_visible=True` draws a small dot at each `(timestamp, value)` sample so individual points stand out from the connecting line. `point_markers_radius` (in pixels) sizes them. Useful when the sample rate is low enough that the points themselves carry information, not just the line through them.

```python order=line,data
import deephaven.plot.tradingview_lightweight as tvl
data = tvl.data.values()

line = tvl.line(
    data,
    timestamp="Timestamp",
    value="Value",
    point_markers_visible=True,
    point_markers_radius=3,
)
```

Each daily sample is now marked with a small filled circle on top of the line.

### Pulse the last point with `last_price_animation`

`last_price_animation` controls the pulse effect on the last-price marker. `"disabled"` (the default) leaves it static, `"continuous"` pulses while the chart is visible, and `"on_data_update"` pulses only when new data arrives. Reach for it on live tickers where the most recent point should draw the eye.

```python order=line,data
import deephaven.plot.tradingview_lightweight as tvl
data = tvl.data.values()

line = tvl.line(
    data,
    timestamp="Timestamp",
    value="Value",
    last_price_animation="continuous",
)
```

The last-price marker now pulses continuously. Use `"disabled"` whenever snapshot determinism matters. The other two modes introduce time-dependent rendering.

### Crosshair marker and line visibility

When the user hovers, LWC draws a marker where the crosshair meets the line. Pass `crosshair_marker=tvl.crosshair_marker(...)` (returns a `CrosshairMarker`) to style it: `visible` toggles it, `radius` sizes it (pixels), and `border_color` / `background_color` / `border_width` style it. `line_visible=False` hides the connecting line itself, handy when you only want the point markers or the crosshair dot.

```python order=line,data
import deephaven.plot.tradingview_lightweight as tvl
data = tvl.data.values()

line = tvl.line(
    data,
    timestamp="Timestamp",
    value="Value",
    line_visible=True,
    crosshair_marker=tvl.crosshair_marker(
        visible=True,
        radius=6,
        border_color="accent-400",
        background_color="accent-200",
        border_width=2,
    ),
)
```

These crosshair-marker options apply to `area` and `baseline` series too.

### Time-proportional axis with `continuous`

By default (`continuous=True`) the chart’s time axis is laid out proportionally: points that are far apart in time are far apart on screen, and real time holes (a market close, a halted feed, missing rows) render as open gaps. Set `continuous=False` to opt back into the plain ordinal layout, where every consecutive point is one equal step apart and time holes collapse.

The axis layout is chart-wide: a single series with `continuous=False` switches the whole chart (all panes and overlays) back to ordinal. The parameter exists on every time-axis series type — for the bar-family behavior (bin-spanning bars, candle gaps) see [histogram](histogram.md#continuous-bars), [candlestick](candlestick.md#time-proportional-layout-with-continuous), and [bar](bar.md#time-proportional-layout-with-continuous). Numeric-axis charts (`yield_curve`, `options_chart`, `custom_numeric`) are unaffected.

```python order=line,data
import deephaven.plot.tradingview_lightweight as tvl
data = tvl.data.values()

line = tvl.line(data, timestamp="Timestamp", value="Value", continuous=False)
```

## API Reference

The result is a single-series TvlChart that can be displayed
directly OR passed to chart() to be combined with other series
under shared chart styling.

**Returns:** `TvlChart` A chart wrapping a single line series (or one line per partition when by is set).

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "line", "parameters": [{"name": "table", "type": "Any", "description": "Deephaven table with the data."}, {"name": "timestamp", "type": "str", "description": "Column name for the time axis."}, {"name": "value", "type": "str", "description": "Column name for the y-axis."}, {"name": "color", "type": "Optional[Color]", "description": "Line color (CSS string).", "default": "None"}, {"name": "line_width", "type": "Optional[LineWidth]", "description": "Stroke width 1\u20134 px.", "default": "None"}, {"name": "line_style", "type": "Optional[LineStyle]", "description": "Dash pattern; see LineStyle.", "default": "None"}, {"name": "line_type", "type": "Optional[LineType]", "description": "Geometry between data points; see LineType.", "default": "None"}, {"name": "line_visible", "type": "Optional[bool]", "description": "Show the line itself (set False to render only crosshair / point markers).", "default": "None"}, {"name": "point_markers_visible", "type": "Optional[bool]", "description": "Show point markers at every data point.", "default": "None"}, {"name": "point_markers_radius", "type": "Optional[float]", "description": "Point marker radius in pixels.", "default": "None"}, {"name": "crosshair_marker", "type": "Optional[CrosshairMarker]", "description": "Styling for the crosshair marker dot; build with crosshair_marker().", "default": "None"}, {"name": "last_price_animation", "type": "Optional[LastPriceAnimationMode]", "description": "Last-price dot animation; see LastPriceAnimationMode.", "default": "None"}, {"name": "last_value_visible", "type": "Optional[bool]", "description": "Show the last-value badge.", "default": "None"}, {"name": "title", "type": "Optional[str]", "description": "Title in the series tooltip / legend.", "default": "None"}, {"name": "visible", "type": "Optional[bool]", "description": "Series visibility.", "default": "None"}, {"name": "price_scale_id", "type": "Optional[str]", "description": "Price-scale ID.", "default": "None"}, {"name": "price_format", "type": "Optional[PriceFormat]", "description": "Per-series price format.", "default": "None"}, {"name": "last_price_line", "type": "Optional[LastPriceLine]", "description": "Styling for the auto last-price horizontal rule; build with last_price_line().", "default": "None"}, {"name": "base_line", "type": "Optional[BaseLine]", "description": "Styling for the zero/index base line; build with base_line().", "default": "None"}, {"name": "price_scale", "type": "Optional[PriceScale]", "description": "Options for the price scale this series binds to; build with price_scale().", "default": "None"}, {"name": "color_column", "type": "Optional[str]", "description": "Column name supplying per-row line color.", "default": "None"}, {"name": "pane", "type": "Optional[int]", "description": "Pane index.", "default": "None"}, {"name": "markers", "type": "Optional[list[Marker]]", "description": "Static markers.", "default": "None"}, {"name": "price_lines", "type": "Optional[list[PriceLine]]", "description": "Horizontal price lines.", "default": "None"}, {"name": "marker_spec", "type": "Optional[MarkerSpec]", "description": "Table-driven marker spec.", "default": "None"}, {"name": "continuous", "type": "bool", "description": "True (default) lays the chart's time axis out proportionally, so real time gaps render as open gaps. False opts the whole chart back to the plain ordinal layout (gaps collapse to one step).", "default": "True"}, {"name": "by", "type": "Optional[str]", "description": "Column name to partition the table by. When set, one runtime series is created per unique value; new partition keys discovered at runtime (ticking tables) add new series automatically.", "default": "None"}, {"name": "on_press", "type": "Optional[PressEventCallable]", "description": "Server-side callback invoked when the user presses (clicks) on the chart. Receives a TvlPressEvent dict, or no argument.", "default": "None"}, {"name": "on_double_press", "type": "Optional[PressEventCallable]", "description": "Server-side callback invoked when the user double-presses on the chart.", "default": "None"}]}} />
Create a CrosshairMarker for a series' crosshair_marker= argument.

**Returns:** `CrosshairMarker` A crosshair-marker config for crosshair_marker=.

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "crosshair_marker", "parameters": [{"name": "visible", "type": "bool | None", "description": "Show the crosshair marker dot.", "default": "None"}, {"name": "radius", "type": "float | None", "description": "Marker radius in pixels.", "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": "Marker border color.", "default": "None"}, {"name": "background_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": "Marker fill color.", "default": "None"}, {"name": "border_width", "type": "float | None", "description": "Marker border width in pixels.", "default": "None"}]}} />
