# Custom Canvas

CanvasLayer: free-form 2D overlays from Rust indicators and strategies. Mirrors the Python custom_layer API; same wire format.

<a id="canvaslayer"></a>
## CanvasLayer

```rust
CanvasLayerCanvasLayer::new(key)
```

Free-form 2D canvas overlay built on the chart side.

Mirrors the Python [`custom_layer(...)`](https://quantchartsllc.com/docs/python/py-custom-draw.md#custom_layer) builder. Build a sequence of style + path + render ops, then attach to an [`IndicatorOutput`](https://quantchartsllc.com/docs/rust/rust-indicator.md#indicatoroutput) or [`SignalOutput`](https://quantchartsllc.com/docs/rust/rust-strategy.md#signaloutput) via `.with_custom_layer(layer.into_spec())`.

Two coordinate spaces:
- **Chart**: `(ts_ms, price)` resolved through `timeToX / priceToY` at draw time so pan/zoom respects them.
- **Pixel**: raw CSS pixels for HUDs glued to the viewport.

Viewport sentinels (`CoordRef::YMin`, `YMax`, `XMin`, `XMax`) span the visible price/time range without knowing the scale.

Use for visuals no predefined plot type covers: candle-style overlays, custom histograms with arbitrary geometry, text annotations, vertical TF boundaries, R-multiple labels above trades.

### Parameters

- `key` (`&str`, default `required`): Unique key. Same key in two indicators collides.

### Returns

CanvasLayer (chainable)

### Example

```rust
use qc_strategy_api::prelude::*;

let mut layer = CanvasLayer::new("tf-boundaries").z(ZOrder::Top);
layer = layer.style(StyleDelta::default()
    .stroke("#ffffff44")
    .line_width(1.0)
    .line_dash(vec![4.0, 3.0]));

for ms in boundaries {
    layer = layer
        .begin()
        .move_to_chart_sentinel(ms, CoordRef::YMin)
        .line_to_chart_sentinel(ms, CoordRef::YMax)
        .stroke();
}

IndicatorOutput::new().with_custom_layer(layer.into_spec())
```

### Notes

- Hard cap of 50,000 ops per spec at the renderer.
- Unknown ops are silently ignored at render time.

<a id="canvaslayer-style"></a>
## CanvasLayer.style

```rust
CanvasLayer.style.style(StyleDelta)
```

Set sticky drawing style. Folds into defaults if called before any path op.

Style is sticky: every following stroke/fill/text uses the most recent values. Calling `.style(...)` before the first op folds into defaults; after that, each call emits a `setStyle` op.

### Example

```rust
layer = layer.style(StyleDelta::default()
    .fill_style("#08080bcc")
    .stroke_style("#1f1f26")
    .line_width(1.0)
    .global_alpha(0.9)
    .font("12px Inter")
    .line_dash(vec![4.0, 4.0]));
```

<a id="canvaslayer-pathrender-ops"></a>
## CanvasLayer path/render ops

`begin / close / stroke / fill / move_to_* / line_to_* / rect_chart / arc_chart / text_chart / *_pixel` builder methods.

Two flavors per coordinate space: `*_chart(ts_ms, price)` and `*_pixel(x_px, y_px)`. Use `*_chart_sentinel(v, CoordRef::YMin)` to anchor an axis at the viewport edge.

Width/height/radius args are always CSS pixels even in chart space - keeps HUD-like badges the same size at any zoom.

### Example

```rust
// triangle marker at every entry tick
for &i in &entry_indices {
    let p = data.mid[i];
    layer = layer
        .style(StyleDelta::default().fill_style("#22c55e88").stroke_style("#22c55e"))
        .begin()
        .move_to_chart(data.timestamp[i],         p - 0.5)
        .line_to_chart(data.timestamp[i] + 60_000, p)
        .line_to_chart(data.timestamp[i],         p + 0.5)
        .close()
        .fill()
        .stroke();
}
```

<a id="coordref-sentinels"></a>
## CoordRef sentinels

`YMin / YMax / XMin / XMax` resolve to the current viewport edges.

Drop a sentinel into the y or x slot when you want the layer to span the visible range without knowing the price/time scale.

Rules:
- `YMin`/`YMax` -> y component of a chart-space point.
- `XMin`/`XMax` -> x component.
- Mixing axes (e.g. `YMin` in the x slot) is rejected at the renderer with a one-time console warning.

### Example

```rust
// vertical line that always spans the visible price range
layer = layer
    .begin()
    .move_to_chart_sentinel(ts_ms, CoordRef::YMin)
    .line_to_chart_sentinel(ts_ms, CoordRef::YMax)
    .stroke();
```

<a id="canvaslayer-into_spec"></a>
## CanvasLayer.into_spec

```rust
CanvasLayer.into_spec.into_spec() -> CustomDrawSpec
```

Finalize the layer for attachment to IndicatorOutput / SignalOutput.

Consumes the builder. Pass the result to `.with_custom_layer(spec)`. Building two specs from one layer requires re-cloning before each `into_spec()`.

<a id="zorder"></a>
## ZOrder

`Bottom / Normal / Top` controls draw order vs. price series.

`Bottom` draws under candles (regime backgrounds). `Normal` draws between candles and overlays. `Top` draws above everything (HUDs, TF boundaries, annotations).

Default is `Normal`. Set via `.z(ZOrder::Top)` on the builder.
