Drawing
Drawing
The contracts for chart drawing tools: the tool interface and base class, the
time/price anchor model, the per-frame render context, hit-test geometry helpers, the
discovery attributes/descriptors, and the line/placement enums. All types live under
SabrTrader.Pipeline.Drawing (Fibonacci helpers under SabrTrader.Pipeline.Drawing.Tools).
On this page
- IDrawingTool
- DrawingToolBase
- DrawingAnchor
- DrawingGeometry
- IDrawingToolRenderContext
- Visual order, resize grips & text
- Drawings that own their grips
- Drawings that describe a trade
- Per-timeframe visibility
- DrawingToolMetadataAttribute
- DrawingToolPropertyAttribute
- LineStyle
- DrawingPlacementMode
- DrawingScope
- ElliottDegree
- FibLevels
Drawing tools: SabrTrader.Pipeline.Drawing
IDrawingTool INTERFACE
One placed instance of a drawing tool (trend line, ray, horizontal line, …). State, render,
and hit-test all live on the tool; the chart's drawing layer owns the list and coordinates the
lifecycle. Most authors derive from DrawingToolBase rather
than implementing this directly.
| Member | Description |
|---|---|
Guid Id { get; } |
Process-unique identity, generated at construction. DrawingToolBase adds a setter so a saved drawing is restored with its id; treat it as immutable. |
string ToolName { get; } |
Display-only name (e.g. "Trend Line"); sourced from DrawingToolMetadataAttribute. |
ChartPanelKey PanelId { get; set; } |
Panel the tool belongs to: a ChartPanelKey (series index plus panel id). ChartPanelKey.Main, equal to default, is the primary series' main candle panel; any other key names a subpanel. Render and hit-test filter by this. |
bool IsSelected { get; set; } |
True when selected (handles drawn, draggable, deletable). |
bool IsPlaced { get; set; } |
True once the user has finalised the tool (all anchors placed). |
bool IsLocked { get; set; } |
A locked drawing cannot be selected by clicking, moved or deleted until it is unlocked. See below. |
int AnchorCount { get; } |
Currently-placed anchor count. |
int MaxAnchorCount { get; } |
Upper bound on anchor count. Fixed tools: equals AnchorCount; variable tools: a cap or int.MaxValue. |
DrawingPlacementMode PlacementMode { get; } |
How the placement state machine gathers anchors. |
bool CanAddAnchor { get; } |
True when another anchor can still be appended (AnchorCount < MaxAnchorCount). |
IReadOnlyList<DrawingAnchor> Anchors { get; } |
Currently-set anchors; uninitialised slots are zero-valued. |
DrawingAnchorCoordinates AnchorCoordinates { get; } |
Which anchor components the tool reads. Default member: TimeAndPrice. See below. |
bool UsesCornerAndSideHandles { get; } |
True for an axis-aligned two-anchor box that resizes from eight grips. Default member: false. See below. |
ChartColor Color { get; set; } |
Stroke colour. |
double LineWidthPx { get; set; } |
Line width in pixels (> 0). |
LineStyle LineStyle { get; set; } |
Stroke style. |
DrawingScope Scope { get; set; } |
Whether the drawing shows on this chart only (default) or on every open chart of the same symbol. See DrawingScope. |
DrawingVisibility Visibility { get; } |
On which timeframes and bar types the drawing shows; on every other chart it is neither painted nor clickable. Default member: DrawingVisibility.Everywhere. DrawingToolBase makes it an editable, saved property with a setter. See Per-timeframe visibility. |
int ZOrder { get; set; } |
Position in the chart's visual stack; below DrawingZOrder.PriceLevel paints under the candles. See below. |
void SetAnchor(int index, DrawingAnchor anchor) |
Set a single anchor by index; validates against AnchorCount. |
void AddAnchor(DrawingAnchor anchor) |
Append an anchor (open-ended / drag-to-paint). Throws InvalidOperationException when CanAddAnchor is false. |
bool RemoveLastAnchor() |
Remove the most-recently-appended anchor; false when nothing to remove. |
void Move(TimeSpan dTime, double dPrice) |
Move all anchors by a (time, price) delta. Constrained tools override. |
void OnRender(IDrawingToolRenderContext ctx) |
Render the tool; called every repaint frame. |
bool HitTest(double pixelX, double pixelY, IDrawingToolRenderContext ctx, double tolerancePx = 6) |
True when the pixel point hits the tool within tolerance; drives hover-highlight + click-to-select. |
IDrawingTool.cspublic interface IDrawingTool
{
Guid Id { get; }
string ToolName { get; }
ChartPanelKey PanelId { get; set; }
bool IsSelected { get; set; }
bool IsPlaced { get; set; }
bool IsLocked { get; set; }
int AnchorCount { get; }
int MaxAnchorCount { get; }
DrawingPlacementMode PlacementMode { get; }
bool CanAddAnchor { get; }
IReadOnlyList<DrawingAnchor> Anchors { get; }
DrawingAnchorCoordinates AnchorCoordinates => DrawingAnchorCoordinates.TimeAndPrice;
bool UsesCornerAndSideHandles => false;
ChartColor Color { get; set; }
double LineWidthPx { get; set; }
LineStyle LineStyle { get; set; }
DrawingScope Scope { get; set; }
DrawingVisibility Visibility => DrawingVisibility.Everywhere;
int ZOrder { get; set; }
void SetAnchor(int index, DrawingAnchor anchor);
void AddAnchor(DrawingAnchor anchor);
bool RemoveLastAnchor();
void Move(TimeSpan dTime, double dPrice);
void OnRender(IDrawingToolRenderContext ctx);
bool HitTest(double pixelX, double pixelY, IDrawingToolRenderContext ctx, double tolerancePx = 6);
}
DrawingToolBase
Default IDrawingTool base. Anchor storage, identity, selection, default styling
(Color / LineWidth / LineStyle), the Scope, Locked and Visibility settings, the stack position, and
reading the metadata attribute for ToolName all live in the base, so a subclass only implements OnRender + HitTest. Two
construction modes: fixed-anchor (base(anchorCount: N)) pre-allocates N slots and
runs DrawingPlacementMode.FixedAnchors; variable-anchor
(base(mode, maxAnchorCount, initialAnchorCount)) starts at the initial count and grows via
AddAnchor.
| Member | Description |
|---|---|
protected DrawingToolBase(int anchorCount) |
Fixed-anchor ctor: pre-allocates anchorCount default anchors; mode = FixedAnchors. Throws if anchorCount < 1. |
protected DrawingToolBase(DrawingPlacementMode mode, int maxAnchorCount = int.MaxValue, int initialAnchorCount = 0) |
Variable-anchor ctor. Throws ArgumentException if mode == FixedAnchors (use the int ctor). |
int MinAnchorsForFinalize { get; protected set; } |
Minimum anchors before a finalize gesture is accepted (open-ended / drag-to-paint). Defaults to 2. |
ChartColor Color { get; set; } |
Stroke colour; annotated [DrawingToolProperty]. Defaults to DodgerBlue. |
LineStyle LineStyle { get; set; } |
Stroke pattern; annotated [DrawingToolProperty]. Defaults to Solid. |
double LineWidthPx { get; set; } |
Stroke thickness in px; annotated [DrawingToolProperty]. Setter throws ArgumentOutOfRangeException when <= 0. |
DrawingScope Scope { get; set; } |
Attach-to setting; annotated [DrawingToolProperty] (Category "Behavior"). Defaults to ThisChart. |
bool IsLocked { get; set; } |
Lock setting; annotated [DrawingToolProperty] (Category "Behavior"). Saved with the drawing, left out of drawing templates. |
DrawingVisibility Visibility { get; set; } |
Per-timeframe visibility; annotated [DrawingToolProperty] (Category "Visibility"). Defaults to DrawingVisibility.Everywhere. Saved with the drawing and copied by drawing templates. Replace the whole value to change it (the type is immutable). The setter throws ArgumentNullException on null. |
int ZOrder { get; set; } |
Stack position; defaults to DrawingZOrder.PriceLevel (in front of price). Saved with the drawing; the chart's Arrange commands change it. |
public virtual DrawingAnchorCoordinates AnchorCoordinates { get; } |
Returns TimeAndPrice. Override for a time-only or price-only tool. |
public virtual bool UsesCornerAndSideHandles { get; } |
Returns false. Override to return true for an axis-aligned two-anchor box. |
protected void DrawLine(IDrawingToolRenderContext ctx, double x0, double y0, double x1, double y1) |
Convenience stroke through the renderer using the tool's own Color, LineWidthPx and LineStyle. |
protected void DrawLine(IDrawingToolRenderContext ctx, double x0, double y0, double x1, double y1, ChartColor color, double widthPx) |
Same, with an explicit colour and width (still applies the tool's LineStyle). |
public abstract void OnRender(IDrawingToolRenderContext ctx) |
Implement to paint the tool. |
public abstract bool HitTest(double pixelX, double pixelY, IDrawingToolRenderContext ctx, double tolerancePx = 6) |
Implement the hit-test. |
public virtual void Move(TimeSpan dTime, double dPrice) |
Shifts every anchor by the delta; override for constrained tools. |
The interface members (Id, ToolName, PanelId,
IsSelected, IsPlaced, AnchorCount, MaxAnchorCount,
PlacementMode, CanAddAnchor, Anchors, SetAnchor,
AddAnchor, RemoveLastAnchor) are all implemented by the base. The example below
is a box that resizes from its corners and side midpoints.
AcmeRectangle.csusing SabrTrader.Pipeline.Drawing;
[DrawingToolMetadata("Acme Rectangle", Category = "Shapes", AnchorCount = 2)]
public sealed class AcmeRectangle : DrawingToolBase
{
public AcmeRectangle() : base(anchorCount: 2) { }
public override bool UsesCornerAndSideHandles => true;
public override void OnRender(IDrawingToolRenderContext ctx)
{
var p1 = ctx.AnchorToPixel(Anchors[0]);
var p2 = ctx.AnchorToPixel(Anchors[1]);
DrawLine(ctx, p1.X, p1.Y, p2.X, p1.Y);
DrawLine(ctx, p2.X, p1.Y, p2.X, p2.Y);
DrawLine(ctx, p2.X, p2.Y, p1.X, p2.Y);
DrawLine(ctx, p1.X, p2.Y, p1.X, p1.Y);
if (ctx.IsSelected) AxisAlignedBoxResize.DrawHandles(ctx, p1.X, p1.Y, p2.X, p2.Y);
}
public override bool HitTest(double px, double py, IDrawingToolRenderContext ctx, double tol = 6)
{
var p1 = ctx.AnchorToPixel(Anchors[0]);
var p2 = ctx.AnchorToPixel(Anchors[1]);
return DrawingGeometry.DistanceToRectangleOutline(px, py, p1.X, p1.Y, p2.X, p2.Y) <= tol;
}
}
DrawingAnchor
A single anchor point: a (time, price) pair in the bar series' canonical timeline. Anchors are
bar-index-independent, so a tool survives live bar updates, scroll, replays, and bar-type changes.
Time-only tools ignore Price; price-only tools ignore Time; the unused
coordinate is stored as a sentinel and never read.
DrawingAnchor.cspublic readonly record struct DrawingAnchor(DateTime Time, double Price)
{
public static DrawingAnchor PriceOnly(double price); // Time = DateTime.MinValue
public static DrawingAnchor TimeOnly(DateTime time); // Price = double.NaN
}
DrawingGeometry
Static 2-D geometry helpers for hit tests. All inputs are pixel coordinates, so call them after
converting anchors via IDrawingToolRenderContext.AnchorToPixel. Outline helpers return a
non-negative distance; compare it against the tool's pixel tolerance.
| Signature | Description |
|---|---|
double DistanceToSegment(px, py, x1, y1, x2, y2) |
Perpendicular distance to a finite line segment. |
double DistanceToRay(px, py, x1, y1, x2, y2) |
Distance to a ray from (x1,y1) through (x2,y2) extending to infinity. |
double DistanceToHorizontalLine(py, lineY) |
Distance to an infinite horizontal line at y = lineY. |
double DistanceToVerticalLine(px, lineX) |
Distance to an infinite vertical line at x = lineX. |
double DistanceToHorizontalRay(px, py, lineY, startX, endX) |
Distance to a horizontal ray from startX (to endX or +∞). |
double DistanceToRectangleOutline(px, py, x1, y1, x2, y2) |
Distance to the closest edge of an axis-aligned rectangle (outline only). |
double DistanceToClosedPolyline(px, py, ReadOnlySpan<(double X, double Y)> points) |
Distance to a closed polyline (last point auto-connects to first). |
double DistanceToOpenPolyline(px, py, ReadOnlySpan<(double X, double Y)> points) |
Distance to an open polyline. |
double DistanceToCircleOutline(px, py, cx, cy, radius) |
Absolute distance to a circle outline. |
double DistanceToEllipseOutline(px, py, x1, y1, x2, y2) |
Distance to an axis-aligned ellipse outline (64-sample approximation). |
(double X, double Y) QuadraticBezier((double X, double Y) p0, (double X, double Y) p1, (double X, double Y) p2, double t) |
Evaluate a quadratic Bézier at t ∈ [0, 1]. |
IDrawingToolRenderContext INTERFACE
Per-frame context handed to OnRender and HitTest. Encapsulates the renderer,
viewport, plot-area rectangle, selection state, and time→pixel conversion. All the plumbing lives
here so tool authors write almost-trivial render/hit-test methods. The instrument-metadata members
default to null/identity so headless and test shims need not supply them.
| Member | Description |
|---|---|
IChartRenderer Renderer { get; } |
Active renderer for the current frame. |
ChartViewport Viewport { get; } |
Bar↔pixel + price↔pixel transforms for the current paint. |
PlotAreaRect PlotArea { get; } |
Pixel rectangle of the main candle pane (excludes axis strips). |
bool IsSelected { get; } |
True when the tool being rendered/hit-tested is selected. |
double TimeToBarIndex(DateTime time) |
Convert an anchor time to a (possibly fractional) bar index. |
(double X, double Y) AnchorToPixel(DrawingAnchor anchor) |
Convenience: anchor → pixel (X, Y), combining TimeToBarIndex + viewport transforms. |
void DrawHandle(double x, double y, ChartColor? fill = null, double sizePx = 7) |
Draw a small selection handle at a pixel point. |
IBarSeries? Bars { get; } |
Bar series driving the chart, when the host has one. Default null. |
double? TickSize { get; } |
Instrument minimum price increment, when known. Default null. |
double? PointValue { get; } |
Currency value of a 1.0 price move per contract, when known. Default null. |
double? PipSize { get; } |
Instrument pip size (forex-style quoting), when known. Default null. |
QuantityUnit QuantityUnit { get; } |
Unit the instrument's quantity is counted in. Default QuantityUnit.Contract. |
double? QuantityStep { get; } |
Smallest tradable quantity increment, when known. Default null. |
IOrderFlowWindowSource? OrderFlowWindows { get; } |
Host seam building volume-at-price profiles for arbitrary windows. Default null. |
Visual order, resize grips & text
| Type | Members |
|---|---|
DrawingZOrder |
static: const int PriceLevel = 0, bool IsBehindPrice(int zOrder). The chart's visual stack is one number line and the price series is a slot on it; a drawing below PriceLevel paints under the candles. The chart's Arrange menu (Bring to Front, Bring Forward, Send Backward, Send to Back) changes ZOrder, and a one-step move may cross under or over price. |
IDrawingTool.IsLocked |
bool, get/set, on the interface and on DrawingToolBase. A locked drawing cannot be selected by clicking, moved or deleted until it is unlocked. It is an editable property, so it persists with the drawing and gets a checkbox in the properties dialog for free. Drawing templates leave it out: a lock is per-instance state, not style, so applying a template (or saving one as a tool default) never locks or unlocks anything. |
IDrawingTool.UsesCornerAndSideHandles |
bool, default false (virtual on DrawingToolBase). Return true for an axis-aligned box stored as two diagonal anchors: the host then lets the user resize it from all four corners and all four side midpoints, not only from the two placed anchors. Rectangle and Gann box opt in. Paint the grips from OnRender while selected: if (ctx.IsSelected) AxisAlignedBoxResize.DrawHandles(ctx, x0, y0, x1, y1);. Hit-testing, the drag and the cursor are the host's. |
AxisAlignedBoxResize |
static: DrawHandles(ctx, x0, y0, x1, y1) paints the eight grips (four corners, four side midpoints) of the box spanned by those pixel corners. Call it from OnRender while the tool is selected. The host hit-tests the grips (corners win over side midpoints on a small box), shows the resize cursor and runs the drag for every tool that UsesCornerAndSideHandles: a corner moves the time of one anchor and the price of the other when the box was drawn "backwards", a side moves only the edge it owns, and pulling an edge through the opposite one keeps following the pointer. |
IDrawingTool.ZOrder |
int, get/set. Ordinary state on DrawingToolBase: inherited, persisted, nothing to implement. |
DrawingAnchorCoordinates |
enum: TimeAndPrice (default), TimeOnly (vertical line), PriceOnly (horizontal line). Declared through IDrawingTool.AnchorCoordinates (virtual on the base) so the properties dialog's coordinate editor never offers a field the tool ignores. |
IDrawingToolTextContent |
string Text { get; set; }, double FontSizePx, bool Bold, bool Italic, double WrapWidthPx (default member, returns 0). Implement it on a tool whose primary content is text the user types (the built-in Text tool does) and the tool opts into the chart's INLINE editor: placing it drops a caret straight onto the chart, and double-clicking an existing one re-opens that caret, so a three-word annotation never needs the properties dialog. The font members let the editor match the text it replaces, so the caret sits where the glyphs will land. WrapWidthPx is the width in screen pixels the tool wraps its text within, and the caret wraps at the same place; 0 means no wrapping, each line runs as long as it is. In the caret Enter commits and Shift+Enter starts a new line, so Text can hold line breaks ("\r\n", "\n" or "\r"); draw one line per break. This is about the editing GESTURE; which values are configurable is still [DrawingToolProperty]. Worked example: a text box that wraps. |
IDrawingToolWidthGrip |
bool TryHitWidthGrip(double pixelX, double pixelY, IDrawingToolRenderContext ctx, out double originXPx, double tolerancePx = 8), void ResizeFromGrip(double offsetFromOriginPx). For a drawing whose width the user sets by dragging a grip, in screen pixels rather than chart time or price (the Text tool's box). The chart asks for the grip only while the drawing is selected and unlocked. Return true from TryHitWidthGrip when the pointer is on your grip and set originXPx to the pixel X the width is measured from (the box's left edge); it stays fixed for the whole drag. The chart then shows a left-right resize cursor and, while the grip is dragged, calls ResizeFromGrip with the pointer's distance right of that origin (negative when left of it). Clamp it to what your drawing can show. Paint the grip yourself (for example with ctx.DrawHandle) while ctx.IsSelected. |
LabeledLineToolBase : DrawingToolBase
|
Base for line tools carrying a user-typed label: string Text, DrawingLabelPosition LabelPosition, DrawingLabelSide LabelSide, double LabelFontSizePx, ChartColor LabelColor, bool LabelBold, bool LabelItalic; protected void DrawLabel(ctx, x0, y0, x1, y1) called at the end of OnRender with the segment just stroked. Empty Text renders nothing. |
DrawingLabelPosition · DrawingLabelSide
|
enum Start, Middle, End; and Above, Below. |
Drawings that own their grips
A drawing implements IDrawingToolGrips when it is resized through grips of its own instead of
by dragging its stored anchors. The built-in Position Sizing tool does: its target is calculated, not stored,
and its stop, entry and target each move without dragging the others along. The chart asks for a grip only
while the drawing is selected and unlocked, shows the grip's resize cursor over it, and reports the pointer
for as long as the grip is held. The chart offers no anchor drag for such a drawing: every resize runs
through DragGrip, and pressing anywhere else on the drawing moves it as a whole. The drawing
paints its grips itself, from OnRender while ctx.IsSelected (for example with
ctx.DrawHandle). Guide with an example:
Own grips and trade plans.
| Type | Members |
|---|---|
IDrawingToolGrips |
bool TryHitGrip(double pixelX, double pixelY, IDrawingToolRenderContext ctx, out DrawingGrip grip, double tolerancePx = 8): return true when the pointer is on one of the drawing's grips and set grip to it. void DragGrip(DrawingGrip grip, DrawingAnchor pointer): the held grip was dragged to pointer, the chart time and price under the pointer. The drawing takes the component the grip owns (the price for a grip that moves in price, the time for one that moves in time) and ignores a position it cannot show. |
DrawingGrip (readonly record struct) |
DrawingGrip(int Id, bool IsHorizontal). Id: the drawing's own number for the grip. The chart hands it back unchanged on every step of the drag and gives it no meaning of its own. IsHorizontal: which resize cursor the chart shows, left-right when true (the grip moves in time), up-down when false (the grip moves in price). |
Drawings that describe a trade
A drawing implements IDrawingToolTradePlan when it describes a trade: a side, an entry, a stop
and a target with a size, as a position-sizing box does. The chart then offers to place that trade as an
order from the drawing's own menu. A tool that does not implement the interface is never offered. The
drawing only describes the trade. The host's trader places it, through IDrawingTradePlanPlacer,
which the host implements and the chart calls. The menu item appears only when the host has a trader, the
drawing sits in the price panel of the chart's primary series, and TryGetTradePlan returns
true.
| Type | Members |
|---|---|
IDrawingToolTradePlan |
bool TryGetTradePlan(IDrawingToolRenderContext ctx, out DrawingTradePlan plan): the trade the drawing describes on the instrument ctx states (TickSize, PointValue, QuantityStep). Return false when the drawing describes no trade that could be sent: the instrument's tick size is unknown, the stop sits on the entry, or the size is zero. |
DrawingTradePlan (readonly record struct) |
DrawingTradePlan(bool IsLong, double EntryPrice, double StopPrice, double TargetPrice, decimal Quantity). IsLong: true for a buy entry (the stop below it, the target above it), false for a sell entry. EntryPrice: the price the entry rests at. StopPrice: the protective stop, on the loss side. TargetPrice: the profit target, on the profit side. Quantity: the size as an order carries it (base-currency units for forex), positive. Every price lies on the instrument's tick grid, so snap the prices to ctx.TickSize before building the plan. |
DrawingTradeEntryKind (enum) |
How the entry of a plan goes out, given where its entry price lies against the market. Limit = 0: the entry rests on the passive side of the market (a buy at or below it, a sell at or above it) and goes out as a limit order. Stop = 1: the market has to reach the entry first (a buy above it, a sell below it) and it goes out as a stop order. |
IDrawingTradePlanPlacer |
Implemented by the host, not by a drawing tool: it is what a chart hands a drawing's trade to. DrawingTradeEntryKind? EntryKindOf(DrawingTradePlan plan): how the entry of plan would go out at this moment, or null when the trader has no live price to tell a limit from a stop. The menu item reads it for its label. void Place(DrawingTradePlan plan): places the plan as a limit or stop entry protected by its stop and target, deciding the entry kind again from the market at that moment. A plan that cannot be placed is refused and the trader is told why; nothing is sent then. |
Per-timeframe visibility
On which charts a drawing shows. A DrawingVisibility holds one
DrawingTimeframeRule per DrawingTimeframeUnit; the chart classifies itself as a
DrawingTimeframe and paints and hit-tests only the drawings whose visibility
IsVisibleOn it. DrawingToolBase exposes it as the editable, saved
Visibility property, so a tool deriving from it has nothing to implement. Guide with an
example: Per-timeframe visibility.
| Type | Members |
|---|---|
DrawingVisibility (sealed class) |
static DrawingVisibility Everywhere { get; }: visible on every chart, one shared instance and the default of every drawing. bool IsEverywhere { get; }: true when no rule hides the drawing anywhere. DrawingTimeframeRule RuleFor(DrawingTimeframeUnit unit): the rule for one unit; throws ArgumentOutOfRangeException for an undeclared unit. DrawingVisibility With(DrawingTimeframeUnit unit, DrawingTimeframeRule rule): a copy with that unit's rule replaced (returns the same instance when the rule is unchanged); throws ArgumentOutOfRangeException for a rule the unit cannot hold (a time unit needs 1 <= From <= To <= RangeMax, any other unit From = To = 0). bool IsVisibleOn(DrawingTimeframe timeframe): true when the drawing shows on that chart; an unknown timeframe is always visible, a time chart above its unit's top counts as the top. Allocation-free, safe to call per drawing per frame. Immutable, value equality (Equals, GetHashCode), shared freely between drawings, clones and templates. |
DrawingTimeframeRule (readonly record struct) |
DrawingTimeframeRule(bool Enabled, int From, int To). Enabled: the drawing shows on charts of this unit at all. From / To: for a time unit, the inclusive range of periods it shows on (Hours 1 to 4); 0 and 0 for a unit without a range. static DrawingTimeframeRule Full(DrawingTimeframeUnit unit): the rule that shows on every chart of the unit (true, 1, RangeMax for a time unit, true, 0, 0 otherwise). |
DrawingTimeframe (readonly record struct) |
What a chart shows. DrawingTimeframeUnit Unit { get; }; int Value { get; }: units per bar for a time chart (at least 1), 0 for the other families; bool IsKnown { get; }: false for Unknown, and Unit is meaningless then. static DrawingTimeframe Unknown { get; }: no bar type yet (chart still loading); every drawing is visible. static DrawingTimeframe Time(DrawingTimeframeUnit unit, int value): a time chart; throws for a unit that is not a time unit or a value below 1. static DrawingTimeframe Family(DrawingTimeframeUnit unit): a chart of a family without a range (Ticks, Range, Volume, Renko, Other); throws for a time unit. static DrawingTimeframe Of(BarSpecification? spec): classifies a bar type; Heikin-Ashi counts as the bars it is built from, a bar type this build does not know (a plugin's) counts as Other, null is Unknown. static DrawingTimeframe OfPeriod(TimeSpan period): a period in the largest unit that divides it exactly (2 hours is Hours 2, 90 minutes is Minutes 90, 14 days is Weeks 2); a sub-second period counts as Seconds 1, a non-positive one is Unknown. |
DrawingTimeframeUnit (enum) |
Ticks (a bar every N trades), Seconds (anything that is not a whole minute), Minutes (whole minutes that are not whole hours), Hours (whole hours that are not whole days), Days (whole days that are not whole weeks), Weeks, Range, Volume (a bar every N contracts), Renko (Renko, Median Renko, UniRenko), Other (Kagi, Point & Figure, Line Break and plugin bar types). Values 0 to 9, in the order the Visibility editor lists them. The member names are the saved form. |
DrawingTimeframeUnits (static) |
const int Count = 10: number of units, one rule each. ReadOnlySpan<DrawingTimeframeUnit> All { get; }: every unit in editor order. bool IsDefined(DrawingTimeframeUnit unit): true for a declared unit. bool IsRanged(DrawingTimeframeUnit unit): true for the time units, whose rule carries a From..To range. int RangeMax(DrawingTimeframeUnit unit): top of the range, Seconds 59, Minutes 59, Hours 24, Days 366, Weeks 52, and 0 for a unit without a range. A range reaching the top also covers every larger period of that unit. |
Discovery: attributes & descriptors
DrawingToolMetadataAttribute
Marks a class as a discoverable drawing tool, read at assembly-scan time to populate the
toolbar menu, hotkeys, icons, and anchor-count requirements. One per tool class; no registration
boilerplate. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)].
| Member | Description |
|---|---|
string Name { get; } |
Display name (ctor argument; required, non-blank). Also the tool's identity: the catalog indexes tools by it and a saved drawing is matched back by it, so it must be unique across all loaded tools and stable once shipped. |
string Category { get; init; } |
Toolbar submenu group. Default "Lines". |
string? Hotkey { get; init; } |
Optional global hotkey (e.g. "Alt+Shift+L"). Unique across all loaded tools: when another tool (built-ins first) already has it, this tool loads without a hotkey and the Plugins log says so. |
string? IconKey { get; init; } |
Key into the icon-resource dictionary. |
int AnchorCount { get; init; } |
Anchor points the user clicks before finalising. Default 2. |
int Order { get; init; } |
Display order within the category (lower first). Default 0. |
string? Description { get; init; } |
Tooltip text. |
DrawingToolPropertyAttribute
Marks a tool property as user-editable in the generic properties dialog. Opt-in: infrastructure
properties stay hidden. DrawingToolBase already annotates Color, Line Width, Line Style,
Attach to (Scope), Locked and Visibility, so every subclass surfaces those by default;
decorate your own for extra editors. Annotated values are saved with the drawing when the property type is
bool, string, a number (int, long, float,
double, decimal), an enum, ChartColor, FibLevelSet or
DrawingVisibility. Constraints are advisory (the dialog enforces them; programmatic writes are
not clamped).
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)].
| Member | Description |
|---|---|
string? DisplayName { get; set; } |
Editor label (defaults to property name). |
string? Description { get; set; } |
One-line tooltip. |
string Category { get; set; } |
Group bucket. Default "General". |
int Order { get; set; } |
Order within the category (lower first). |
object? MinValue { get; set; } |
Advisory numeric lower bound. Null = none. |
object? MaxValue { get; set; } |
Advisory numeric upper bound. Null = none. |
object? Step { get; set; } |
Spinner increment. Null = editor decides. |
bool Multiline { get; set; } |
For a string property: the dialog shows a taller box where Enter starts a new line. Ignored for other types. |
Enums
LineStyle
Stroke style for drawing-tool segments. Mirrors PlotStyle's line variants.
| Value | Meaning |
|---|---|
Solid |
Continuous stroke. |
Dashed |
Dashed stroke. |
Dotted |
Dotted stroke. |
DashDot |
Dash, gap, dot, gap, repeating. Value 3. |
DashDotDot |
Dash, gap, dot, gap, dot, gap, repeating. Value 4. |
DrawingPlacementMode
How a tool gathers its anchors during placement. The chart's interaction state machine dispatches on this value; each mode has a distinct user gesture.
| Value | Meaning |
|---|---|
FixedAnchors |
Click once per anchor; placement completes when AnchorCount clicks land. Default for line / rectangle / triangle / arrow. |
OpenEnded |
Click to append anchors, double-click or Escape to finalize (must meet MinAnchorsForFinalize). Used by Path, Polyline. |
DragToPaint |
Mouse-down + drag samples points; mouse-up finalizes. Used by Brush, Highlighter. |
DrawingScope
Where a placed drawing is shown. Surfaced on IDrawingTool.Scope as an "Attach to"
[DrawingToolProperty]; each member's [Description] is the dropdown label the
editor renders in place of the raw name.
| Value | Meaning |
|---|---|
ThisChart |
"This chart": the drawing lives only on the chart it was placed on (default). |
AllCharts |
"All charts (same symbol)": the drawing is shared and kept in sync across every open chart of the same symbol. |
ElliottDegree
Wave degree for Elliott-wave labelling tools, largest to smallest. Each member's
[Description] is its dropdown label (with the notation shown in parentheses).
ElliottDegrees.Label(ElliottDegree degree, string key) formats a single wave label for a
degree and key.
| Value | Notation |
|---|---|
GrandSupercycle |
[I] / [a] |
Supercycle |
(I) / (a) |
Cycle |
I / a |
Primary |
[1] / [A] |
Intermediate |
(1) / (A) |
Minor |
1 / A |
Minute |
i / a |
Minuette |
(i) / (a) |
Subminuette |
i / a |
Micro |
1 / A |
Submicro |
(1) / (A) |
Miniscule |
1 / A |
Fibonacci levels: SabrTrader.Pipeline.Drawing.Tools
FibLevels
Standard Fibonacci ratios used by the Fib retracement / extension / channel / wedge / fan family.
Centralised so changing a default updates every tool at once. Static fields are the raw ratio ladders;
the Default*Set() factories build a ready-to-edit FibLevelSet.
| Member | Description |
|---|---|
static readonly double[] StandardRetracements |
{ 0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0 }: classic retracement ladder. |
static readonly double[] StandardExtensions |
{ 1.272, 1.414, 1.618, 2.0, 2.618, 3.618, 4.236 }: extension ratios (> 1.0). |
static readonly double[] StandardRetracementsAndExtensions |
{ 0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0, 1.272, 1.618, 2.0, 2.618 }. |
static readonly double[] StandardSpeedRatios |
{ 0.382, 0.5, 0.618 }: speed-resistance fan / arc ratios. |
static readonly int[] StandardTimeZones |
{ 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }: time-zone multiples. |
static readonly double[] StandardGannBoxRatios |
{ 0.25, 0.382, 0.5, 0.618, 0.75 }: the Gann box inner grid, default for both its price and its time lines (the outline already marks 0 and 1). |
static readonly (int Price, int Time)[] GannAngles |
Gann fan slopes from 1×8 to 8×1 (1×1 = central reference). |
static FibLevelSet DefaultRetracementSet() |
Visible retracement ladder paired with default colours. |
static FibLevelSet DefaultExtensionSet() |
Visible retracements + extensions with the extension palette. |
static FibLevelSet DefaultRetracementSetInheritColor() |
Retracement ladder, every level inherits the tool's stroke colour. |
static FibLevelSet DefaultSpeedRatioSetInheritColor() |
Three speed ratios, inherit stroke colour. |
static FibLevelSet DefaultCircleRatioSetInheritColor() |
Fib-circle ring set, inherit stroke colour. |
static FibLevelSet DefaultGannBoxSetInheritColor() |
Gann box grid from StandardGannBoxRatios, inherit stroke colour. Build one set for the price lines and one for the time lines. |
FibLevel: one configurable level (a sealed class):
| Member | Description |
|---|---|
double Ratio { get; set; } |
Ratio applied to the tool's anchor range. |
ChartColor? Color { get; set; } |
Per-level stroke override; null = inherit the tool's stroke colour. |
bool IsVisible { get; set; } |
Render flag. False hides the level without removing it. Default true. |
FibLevel(double ratio, ChartColor? color = null, bool visible = true) |
Convenience constructor (also has a parameterless ctor). |
FibLevel Clone() |
Deep copy. |
FibLevelSet: ordered, mutable collection of levels; inherits
ObservableCollection<FibLevel> so the properties dialog's list editor binds to it directly:
| Member | Description |
|---|---|
FibLevelSet() / FibLevelSet(IEnumerable<FibLevel> items)
|
Empty or seeded from an item sequence. |
FibLevelSet Clone() |
Deep copy: every level is cloned, not shared. |
void ReplaceWith(IEnumerable<FibLevel> other) |
Replace the contents with clones of other in one step, announced by a single Reset notification, so a painting thread never sees the set half-empty. |
ReadOnlySpan<FibLevel> Snapshot { get; } |
The levels as of the last structural change, allocation-free and safe from any thread. Read this on a render thread: enumerating the observable collection itself while the editor mutates it can throw. |
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) |
Runs on every structural change (add, insert, remove, replace, move, clear) and republishes FibLevelSet.Snapshot before the collection-changed listeners run, so a listener that reads it sees the changed set. The class is sealed, so there is nothing to override or call here. |