Plots & rendering
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.
On this page
- IPlot · Plot · PlotStyle
- IOhlcPlot · OhlcPlot
- IIndicatorLine · IndicatorLine
- ChartColor
- IChartRenderer + capability interfaces
- CandleStyle · CandleGeometry · AxisStyle
- ChartViewport · ChartPanelBounds
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. |
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
}