Plots & rendering

Reference

Plots & rendering

The plot outputs an indicator publishes (lines, OHLC candles, threshold levels), the platform-neutral colour type, the low-level drawing surface for custom render passes, and the viewport that maps bars/prices to pixels.

Plots — TradeStrike.Pipeline.Plots

An indicator exposes its renderable outputs as IPlot objects (one per output column). The chart layer reads them and draws; the indicator owns the underlying series. All plot/line types are mutable so the settings dialog can recolour and restyle at runtime without reconstructing the indicator.

IPlot

One renderable output of an indicator. Values[0] is the newest value; double.NaN marks "no value at this bar" (warmup) and is skipped by the renderer.

Member Description
string Name { get; set; } Legend label (e.g. "SMA(20)", "Upper Band").
ISeries<double> Values { get; } The values to render. Same indexing as ISeries<T>; NaN breaks the line / omits the dot or bar.
PlotStyle Style { get; set; } Rendering style — line, area, histogram, etc.
ChartColor Color { get; set; } Stroke / fill colour.
double LineWidthPx { get; set; } Stroke width in pixels (also area-border + histogram-edge width).
bool Visible { get; set; } When false the plot stays in the output list but the chart skips it (show/hide toggle).
bool ShowPriceMarker { get; set; } When true the chart paints a coloured price-axis pill at the plot's most recent value. Default true.
string? OwnerName { get; } Friendly id of the owning indicator. Default impl returns null.
IIndicator? Owner { get; } Back-reference to the owning indicator (renderer reads its master price-marker gate). Default impl returns null.
PlotSemantics Semantics { get; } Machine-readable meaning of the value (overlay vs oscillator, range, unit). Default impl returns PlotSemantics.Default.

Default-implemented members. OwnerName, Owner and Semantics have interface default impls, so existing third-party IPlot implementations keep compiling. IndicatorBase.AddPlot stamps them at registration time.

Plot

The default IPlot implementation (sealed class). Construct one per output column in OnInit and add it via IndicatorBase.AddPlot(...). Setters validate eagerly — a blank name or non-positive LineWidthPx throws immediately.

Plot.cspublic Plot(string name, ISeries<double> values, PlotStyle style, ChartColor color, double lineWidthPx);

// Mutable: Name, Style, Color, LineWidthPx, Visible, ShowPriceMarker,
//          OwnerName, Owner, Semantics
// Read-only: Values  (set once in the ctor; IS the indicator's output series)
Member Notes
string Name { get; set; } Throws ArgumentException if set to null/whitespace.
string? OwnerName { get; set; } Settable; AddPlot stamps owner identity.
IIndicator? Owner { get; set; } Settable back-pointer to the owning indicator.
ISeries<double> Values { get; } Set in the ctor; never null.
PlotStyle Style { get; set; }
ChartColor Color { get; set; }
double LineWidthPx { get; set; } Throws ArgumentOutOfRangeException if ≤ 0.
bool Visible { get; set; } Defaults to true.
bool ShowPriceMarker { get; set; } Defaults to true.
PlotSemantics Semantics { get; set; } Defaults to PlotSemantics.Default.

PlotStyle

How a plot's values are rendered. The chart's plot render pass picks the primitive sequence from this enum.

Value Meaning
Line Continuous line through consecutive non-NaN values. The default.
Dashed Dashed line — same path as Line with a stroke dash pattern.
Dotted Dotted line — short-on / long-off stroke pattern.
Area Filled area between the value and the zero baseline.
Histogram Vertical bars from zero to the value (positive up, negative down).
Dots Discrete dots at each bar position; no connecting line.
Step Step line — holds each value as a horizontal segment until the next bar, then jumps. Suits OR / IB bounds, prior-day levels.
Candle Indicator-side OHLC candles. The plot MUST implement IOhlcPlot so the renderer has all four series.

IOhlcPlot

A specialised IPlot carrying per-bar OHLC data so the renderer can draw candlesticks. The plot's Values series IS the close series (keeps legend / price-marker / crosshair working). The parallel open/high/low series share the same indexing; any NaN at a bar skips that candle.

Member (adds to IPlot) Description
ISeries<double> OpenValues { get; } Open per bar. Aligned with Values.
ISeries<double> HighValues { get; } High per bar. Aligned with Values.
ISeries<double> LowValues { get; } Low per bar. Aligned with Values.
ChartColor BullishColor { get; set; } Body + wick colour for bullish bars (close >= open).
ChartColor BearishColor { get; set; } Body + wick colour for bearish bars (close < open).
double BodyFillRatio { get; set; } Body width as a fraction of BarSpacingPx. Typical 0.7; 0 = wick-only. Wicks are always 1px.

OhlcPlot

The default IOhlcPlot implementation (sealed class). The big consumer is Cumulative Delta. Style is set to PlotStyle.Candle by the ctor; Color maps to BullishColor so non-OHLC-aware paths see a sane single colour. Note LineWidthPx here is the WICK width.

OhlcPlot.cspublic OhlcPlot(
    string name,
    ISeries<double> openValues,
    ISeries<double> highValues,
    ISeries<double> lowValues,
    ISeries<double> closeValues,   // becomes Values (the close series)
    ChartColor bullishColor,
    ChartColor bearishColor,
    double wickWidthPx = 1.0,        // becomes LineWidthPx; must be > 0
    double bodyFillRatio = 0.7);     // must be in [0, 1]

IIndicatorLine

A horizontal threshold line on an indicator's subgraph — fixed Y-value, no time component (matches NinjaTrader's AddLine(): RSI 30/70, Stochastic 20/80, ADX 20/40).

Member Description
string Name { get; set; } Legend label (e.g. "Upper", "Lower").
double Value { get; set; } Constant Y-value the line renders at.
ChartColor Color { get; set; } Stroke colour.
double LineWidthPx { get; set; } Stroke width in pixels.
PlotStyle Style { get; set; } Solid / dashed / dotted.
bool Visible { get; set; } When false, the renderer skips this line.
bool ParticipatesInAutoScale { get; set; } When true (default), the value folds into the subpanel's Y auto-scale (right for bounded oscillators). When false, drawn/clipped but doesn't stretch the scale (right for ADX/ADXR).

IndicatorLine

The default IIndicatorLine implementation (sealed class). Add one per threshold via IndicatorBase.AddLine(...).

IndicatorLine.cspublic IndicatorLine(
    string name, double value, ChartColor color,
    double lineWidthPx = 1.0,
    PlotStyle style = PlotStyle.Line,
    bool participatesInAutoScale = true);
// name null/whitespace -> ArgumentException; lineWidthPx <= 0 -> ArgumentOutOfRangeException

ChartColor

Platform-neutral RGBA colour (8-bit per channel), declared as a readonly record struct. The renderer converts to SkiaSharp / Direct2D at the boundary.

ChartColor.cspublic readonly record struct ChartColor(byte R, byte G, byte B, byte A = 255);
Member Description
byte R, G, B, A Record positional members. A defaults to 255 (opaque).
static ChartColor Black new(0, 0, 0).
static ChartColor White new(255, 255, 255).
static ChartColor Transparent new(0, 0, 0, 0).
static ChartColor FromArgb(byte a, byte r, byte g, byte b) Build from ARGB byte order.
uint Argb { get; } 32-bit packed ARGB (A=high byte, B=low byte).

Rendering — TradeStrike.Pipeline.Charts.Rendering

The low-level surface a custom render pass writes into, plus the style + geometry value types. Coordinates are already in pixel space (origin top-left, X right, Y down); the renderer knows nothing about bars or prices. Every frame is bracketed by BeginFrame / EndFrame and runs on the render thread, so implementations need not be thread-safe.

IChartRenderer

The core 2D drawing surface (IChartRenderer : IDisposable). Several methods are default-implemented convenience wrappers over the primitives.

Member Description
void BeginFrame(int canvasWidthPx, int canvasHeightPx, ChartColor background) Open a frame; set size + clear with the background. Pair with EndFrame.
void DrawCandle(double centerX, double bodyTopY, double bodyBottomY, double wickTopY, double wickBottomY, double bodyWidthPx, bool bullish, CandleStyle style) Draw one candle (all coords already in pixels).
void DrawLine(double x0, double y0, double x1, double y1, ChartColor color, double widthPx) Stroke a line. Gridlines, ticks, crosshair.
void FillRect(double x, double y, double widthPx, double heightPx, ChartColor color) Filled rectangle.
void DrawRoundedRectOutline(double x, double y, double widthPx, double heightPx, double radiusPx, ChartColor color, double strokeWidthPx) Default impl falls back to four straight edges; path-backed renderers override for true rounded corners.
void FillPolygon(ReadOnlySpan<(double X, double Y)> points, ChartColor color) Filled, auto-closed polygon. Area plots, zone overlays, cluster fills.
void PushClip(double x, double y, double widthPx, double heightPx) Push a rectangular clip; nested pushes intersect.
void PopClip() Pop the topmost clip. Pair 1:1 with PushClip.
void DrawText(double x, double y, string text, ChartColor color, double sizePx, string? fontFamily = null, bool bold = false, bool italic = false) Draw text anchored at its baseline.
void DrawTextCentered(double centerX, double baselineY, string text, ChartColor color, double sizePx, string? fontFamily = null, bool bold = false, bool italic = false) Default impl measures then offsets; renderers with native centring override.
void DrawTextRotated(double x, double y, double angleDegrees, string text, ChartColor color, double sizePx, string? fontFamily = null, bool bold = false, bool italic = false, bool centered = false) Default impl ignores rotation (draws horizontal); transformable renderers override.
(double WidthPx, double HeightPx) MeasureText(string text, double sizePx, string? fontFamily = null, bool bold = false, bool italic = false) Real glyph metrics. Required by label centring + pill sizing.
double FontAscentPx(double sizePx, string? fontFamily = null, bool bold = false, bool italic = false) Default impl approximates ascent as 80% of measured height; override for exact placement.
void EndFrame() Finalize the frame (flush to the surface).

Renderer capability interfaces

Optional capabilities a concrete IChartRenderer MAY also implement. Detect each with a single is check on the renderer you already hold; if it isn't supported, fall back to the core primitive. This honours ISP — no existing implementer is forced to change.

Interface Member(s) Fallback
IBatchCandleRenderer void DrawCandles(ReadOnlySpan<CandleGeometry> candles, CandleStyle style) Per-candle DrawCandle (pixel-equivalent).
IPolylineRenderer void DrawPolyline(ReadOnlySpan<(double X, double Y)> points, ChartColor color, double widthPx) · void DrawDashedPolyline(... , double dashLengthPx, double gapLengthPx) Per-segment DrawLine.
IPaintedCandleRenderer void DrawCandle(double centerX, double bodyTopY, double bodyBottomY, double wickTopY, double wickBottomY, double bodyWidthPx, bool bullish, CandleStyle style, ChartColor overrideColor) Standard DrawCandle.
IEllipseRenderer void FillEllipse(double centerX, double centerY, double radiusX, double radiusY, ChartColor color) FillPolygon with an n-gon.
IGradientRenderer void FillLinearGradient(ReadOnlySpan<(double X, double Y)> polygon, double startX, double startY, ChartColor startColor, double endX, double endY, ChartColor endColor) Flat FillPolygon.
ISphereRenderer void FillSphere(double centerX, double centerY, double radiusPx, ChartColor color) · void FillSphereShadow(double centerX, double centerY, double radiusPx, ChartColor color, double blurPx) FillEllipse / FillPolygon.
Capability detection// Renderer you already hold from the render context:
if (renderer is IPolylineRenderer poly)
    poly.DrawPolyline(points, color, 1.5);
else
    for (int i = 1; i < points.Length; i++)
        renderer.DrawLine(points[i - 1].X, points[i - 1].Y, points[i].X, points[i].Y, color, 1.5);

CandleStyle

User-tunable visual style for candle rendering (sealed class, all init-only). Defaults match the conventional green-up / red-down theme.

Member Default
ChartColor BullishBodyColor { get; init; } new(0x26, 0xa6, 0x9a) (teal-green)
ChartColor BearishBodyColor { get; init; } new(0xef, 0x53, 0x50) (red)
ChartColor BullishWickColor / BearishWickColor { get; init; } match the body colours
ChartColor BullishBorderColor / BearishBorderColor { get; init; } darker body shades
bool HollowBullish { get; init; } false (filled both ways)
double BodyFillRatio { get; init; } 0.7 (fraction of bar spacing used by the body)
double BorderWidthPx { get; init; } 1.0
double WickWidthPx { get; init; } 1.0

Also in this file. ChartCanvasStyle (Background, GridColor, AxisColor, ShowSessionDividers, SessionDividerColor, SessionDividerWidthPx) themes the canvas separately from candles.

CandleGeometry

Pre-computed pixel geometry for one candle (readonly record struct) — the batch-draw analogue of the DrawCandle arguments. All Y values are already viewport-mapped; body top < bottom in screen coords.

CandleGeometry.cspublic readonly record struct CandleGeometry(
    double CenterX,
    double BodyTopY,
    double BodyBottomY,
    double WickTopY,
    double WickBottomY,
    double BodyWidthPx,
    bool Bullish,
    ChartColor? OverrideColor = null);   // non-null = per-bar "paint-bars" colour

AxisStyle

Sizing, colour and label formatting for the price + time axes (sealed class, all init-only). PriceAxisWidthPx / TimeAxisHeightPx double as the canvas margins; set either to 0 to hide that axis.

Member Default
double PriceAxisWidthPx 64 (0 = hide)
double TimeAxisHeightPx 22 (0 = hide)
double TickLengthPx 4
double LabelFontSizePx 11
string FontFamily "Figtree"
bool Bold / Italic false
double LabelPaddingPx 4
ChartColor LabelColor / GridColor / AxisBackground dark-theme defaults
double GridWidthPx 1
bool ShowGridLines true
string PriceFormat "0.##"
double PriceTickPixelSpacing / TimeTickPixelSpacing 40 / 90
double RightMarginPx 20
double TopMarginPx / BottomMarginPx 0
bool Use24HourTime true

Also in this file. CrosshairStyle (LineColor, LineWidthPx, LabelForeground, LabelBackground, LabelFontSizePx, FontFamily, Bold, Italic, LabelPaddingPx, PriceFormat) styles the mouse-follow crosshair.

Viewport — TradeStrike.Pipeline.Charts.Viewport

Pure-math description of what's visible plus the transforms between (bar index, price) and (pixel x, pixel y). Bar index 0 is the oldest visible bar; higher price = lower pixel Y.

ChartViewport

Immutable (sealed class) — pan / zoom / rescale return NEW instances, so render code never observes a partially updated state. The ctor validates every argument (positive canvas, non-negative margins, at least one visible bar, maxPrice > minPrice).

ChartViewport.cspublic ChartViewport(
    int canvasWidthPx, int canvasHeightPx,
    int rightMarginPx, int topMarginPx, int bottomMarginPx,
    int firstVisibleBarIdx, int lastVisibleBarIdx,
    double minPrice, double maxPrice,
    int rightInsetPx = 0, int topInsetPx = 0, int bottomInsetPx = 0);
Member Description
int CanvasWidthPx / CanvasHeightPx Full canvas size.
int RightMarginPx / TopMarginPx / BottomMarginPx Axis-label margins.
int FirstVisibleBarIdx / LastVisibleBarIdx Inclusive visible bar window.
double MinPrice / MaxPrice Visible price range.
int RightInsetPx / TopInsetPx / BottomInsetPx Empty content strips that never contain a candle (clamped so the plot keeps ≥ 1px).
int PlotWidthPx / PlotHeightPx Canvas minus margins.
int BarAreaWidthPx Plot width minus the right inset (bars lay out across this).
int PriceAreaHeightPx / PriceAreaTopPx Vertical band the price range maps across, and its top pixel Y.
int VisibleBarCount Last - First + 1.
double BarSpacingPx Pixels per bar (bar-center to bar-center), over the bar area.
double BarIndexToPixelX(int barIdx) Bar index → centre pixel X.
int PixelXToBarIndex(double pixelX) Inverse; round-to-nearest for hit-testing.
double PriceToPixelY(double price) Price → pixel Y (linear).
double PixelYToPrice(double pixelY) Inverse; extrapolates into the reserved strips.
ChartViewport PanBars(int bars) Shift the window (positive = newer); price range unchanged.
ChartViewport ZoomBars(int newVisibleBarCount, int anchorBarIdx) Change the bar count keeping the anchor centred.
ChartViewport WithPriceRange(double minPrice, double maxPrice) New instance with a replaced price range.
ChartViewport WithCanvasSize(int canvasWidthPx, int canvasHeightPx) New instance with a replaced canvas size.
ChartViewport WithoutRightInset() The margin-free viewport handed to INDICATORS — collapses the axis + right-inset strips so the whole viewport is the drawable bar area, preserving every transform.
Indicators get a margin-free viewport. Custom render passes receive the WithoutRightInset() viewport — measure to PlotWidthPx and the platform margins are already collapsed out, while bars still map to candle-exact pixels.

ChartPanelBounds

Pixel rectangle of one chart PANEL within the canvas (the main price panel or an indicator subpanel), declared as a readonly record struct. Same pixel space as ChartViewport.

ChartPanelBounds.cspublic readonly record struct ChartPanelBounds(
    string PanelId,        // "main" for the price panel, else the subpanel id
    double LeftPx,
    double TopPx,
    double WidthPx,
    double HeightPx)
{
    public double BottomPx { get; }   // TopPx + HeightPx
}