The render context
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). |
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.