The render context

Drawing tools

The render context

The single object passed to both OnRender and HitTest that turns bar-timeline anchors into screen pixels, so your tool code never does the projection maths itself.

What it is

Both abstract methods receive an IDrawingToolRenderContext. It bundles everything a tool needs for one frame: the renderer, the viewport transforms, the plot-area rectangle, the current selection flag, and the time→pixel conversions that depend on the live bar timeline (which the chart owns, not the tool). All the plumbing lives here so your OnRender and HitTest stay almost trivial.

IDrawingToolRenderContext.csusing TradeStrike.Pipeline.Bars;
using TradeStrike.Pipeline.Charts.Rendering;
using TradeStrike.Pipeline.Charts.Viewport;
using TradeStrike.Pipeline.Plots;

namespace TradeStrike.Pipeline.Drawing;

public interface IDrawingToolRenderContext
{
    IChartRenderer Renderer { get; }          // DrawLine / FillRect / DrawText / FillPolygon / ...
    ChartViewport Viewport { get; }           // PriceToPixelY, BarIndexToPixelX, ...
    PlotAreaRect PlotArea { get; }            // pixel rect of the main candle pane
    bool IsSelected { get; }                  // true when THIS tool is selected

    double TimeToBarIndex(System.DateTime time);
    (double X, double Y) AnchorToPixel(DrawingAnchor anchor);   // the one you use most
    void DrawHandle(double x, double y, ChartColor? fill = null, double sizePx = 7);

    // Optional host-supplied data (default null / Contract — degrade gracefully when absent):
    IBarSeries? Bars => null;
    double? TickSize => null;
    double? PointValue => null;
    QuantityUnit QuantityUnit => QuantityUnit.Contract;
    double? QuantityStep => null;
}

Anchors to pixels

The member you reach for most is AnchorToPixel, which projects one anchor to an (X, Y) pixel pair by combining the time→bar-index lookup with the viewport's bar→X and price→Y transforms. When only one axis matters, drop to Viewport directly: PriceToPixelY(price) for a horizontal level, or TimeToBarIndex(time) fed through Viewport.BarIndexToPixelX(...) for a vertical marker. The viewport also exposes PixelYToPrice / PixelXToBarIndex for the inverse.

OnRender.cspublic override void OnRender(IDrawingToolRenderContext ctx)
{
    // Two-anchor projection — both axes:
    var (x1, y1) = ctx.AnchorToPixel(Anchors[0]);
    var (x2, y2) = ctx.AnchorToPixel(Anchors[1]);
    ctx.Renderer.DrawLine(x1, y1, x2, y2, Color, LineWidthPx);

    // Single-axis: a horizontal level at one price across the whole pane:
    double y = ctx.Viewport.PriceToPixelY(Anchors[0].Price);
    ctx.Renderer.DrawLine(ctx.PlotArea.X, y, ctx.PlotArea.Right, y, Color, LineWidthPx);

    if (ctx.IsSelected) { ctx.DrawHandle(x1, y1); ctx.DrawHandle(x2, y2); }
}

The plot area

PlotArea is a PlotAreaRect — the pixel rectangle of the candle pane, excluding the axis strips. It exposes X, Y, Width, Height, plus convenience Right and Bottom. Use PlotArea.Right when a line should extend to the right edge of the pane (rays, Fibonacci levels), and the full rect to clamp drawing inside the panel.

The renderer

Renderer is the platform-neutral IChartRenderer drawing surface. Origin is top-left; X increases left→right, Y increases top→bottom. The primitives a tool typically uses:

Method Draws
DrawLine(x0, y0, x1, y1, color, widthPx) A straight stroke.
FillRect(x, y, w, h, color) A filled rectangle (use alpha for translucency).
FillPolygon(points, color) A filled, auto-closed polygon (arrows, zones).
DrawText(x, y, text, color, sizePx, ...) Text anchored at its baseline.
DrawTextCentered(centerX, baselineY, ...) Horizontally centred text.
MeasureText(text, sizePx, ...) Pixel size of a run, for label layout.
PushClip(x, y, w, h) / PopClip() Clip subsequent drawing to a rectangle (paired 1:1).
Draw handles only when selected. DrawHandle paints a small filled square (outlined by the stroke) at an anchor. Guard the calls with ctx.IsSelected so handles appear only while the tool is selected — the behaviour of every built-in tool.

Instrument-aware tools

Most tools are pure geometry and ignore the rest of the context. Tools that report tick counts or money values (measured risk/reward, position sizing) read TickSize and PointValue — a tick is worth TickSize × PointValue — and label quantities with QuantityUnit instead of assuming "contracts". Bars exposes the live IBarSeries for data-driven tools (anchored VWAP, statistical extensions). All of these default to null (or QuantityUnit.Contract) on headless / test shims, so a tool must degrade gracefully: omit a tick/money read-out rather than guess when the host has no instrument metadata.