Table of Contents

Drawing

An extension draws on the chart, in a column or in a module through the same API: an OnRender override receives a RenderContext and draws with GDI+-like calls. This article shows it for a chart indicator; the columns and modules articles describe when their OnRender is called.

OnRender and the layouts

Override OnRender and set EnableCustomDrawing to true, otherwise the method is not called. Then choose the layouts to draw on with SubscribeToDrawingEvents, passing the DrawingLayouts flags:

  • Historical: drawn on every new candle, on scrolling and on zooming;
  • LatestBar: drawn when the last bar changes, as a rule on every tick;
  • Final: the last layer, drawn on every chart repaint, for example on every mouse move;
  • None: nothing is drawn.

Without the call the indicator is drawn only when the last bar changes. OnRender receives the context and the layout being drawn, so one method can draw different things on different layouts:

SubscribeToDrawingEvents(DrawingLayouts.Final | DrawingLayouts.LatestBar);

RenderContext

The context takes colors as System.Drawing.Color, pens and fonts as RenderPen and RenderFont (namespace OFT.Rendering.Tools); RenderPens holds ready-made pens of the standard colors.

using System.Drawing;

using ATAS.Indicators;
using OFT.Rendering.Context;
using OFT.Rendering.Tools;

namespace ATAS.Docs.Samples.CommonApi;

public class RenderContextIndicator : Indicator
{
    private readonly RenderFont _font = new("Arial", 15);
    private readonly RenderPen _ellipsePen = new(Color.Bisque);

    public RenderContextIndicator()
    {
        EnableCustomDrawing = true;
        SubscribeToDrawingEvents(DrawingLayouts.Final);
    }

    protected override void OnCalculate(int bar, decimal value)
    {
    }

    protected override void OnRender(RenderContext context, DrawingLayouts layout)
    {
        // A rectangle (width 100, height 200) from the point (5, 10)
        context.DrawRectangle(RenderPens.Blue, new Rectangle(5, 10, 100, 200));

        // A filled rectangle (100 x 100) from the point (0, 0)
        context.FillRectangle(Color.DarkSalmon, new Rectangle(0, 0, 100, 100));

        // A line from (10, 20) to (50, 60)
        context.DrawLine(RenderPens.AliceBlue, 10, 20, 50, 60);

        // A string at (50, 60)
        context.DrawString("Sample string", _font, Color.Black, 50, 60);

        // An ellipse inside a rectangle
        context.DrawEllipse(_ellipsePen, new Rectangle(10, 10, 100, 100));
    }
}

Create pens and fonts once, in fields, and reuse them: OnRender is called very often, especially on the Final layout (see Performance).

Coordinates

The origin is the upper-left corner of the chart; X grows to the right, Y grows down.

The coordinate system

A crosshair at the mouse position with the volume of the bar under it:

using System.Drawing;

using ATAS.Indicators;
using OFT.Rendering.Context;
using OFT.Rendering.Tools;

namespace ATAS.Docs.Samples.CommonApi;

public class CrosshairVolumeIndicator : Indicator
{
    // Pens and fonts are created once, not on every frame
    private readonly RenderPen _pen = new(Color.BlueViolet, 4);
    private readonly RenderFont _font = new("Arial", 14);

    public CrosshairVolumeIndicator()
    {
        EnableCustomDrawing = true;

        // The Final layout only: redrawn on every chart repaint, including mouse movement
        SubscribeToDrawingEvents(DrawingLayouts.Final);
    }

    protected override void OnCalculate(int bar, decimal value)
    {
    }

    protected override void OnRender(RenderContext context, DrawingLayouts layout)
    {
        // The horizontal line
        context.DrawLine(_pen, 0, MouseLocationInfo.LastPosition.Y, ChartArea.Width, MouseLocationInfo.LastPosition.Y);

        // The vertical line
        context.DrawLine(_pen, MouseLocationInfo.LastPosition.X, 0, MouseLocationInfo.LastPosition.X, ChartArea.Height);

        var candle = GetCandle(MouseLocationInfo.BarBelowMouse);

        if (candle != null)
        {
            var text = $"Total candle volume={candle.Volume}";
            var textSize = context.MeasureString(text, _font);
            var textRectangle = new Rectangle(MouseLocationInfo.LastPosition.X + 10, MouseLocationInfo.LastPosition.Y + 10, (int)textSize.Width, (int)textSize.Height);

            context.FillRectangle(Color.CornflowerBlue, textRectangle);
            context.DrawString(text, _font, Color.AliceBlue, textRectangle);
        }
    }
}

The volume of the bar under the mouse

Above or below the chart

DrawAbovePrice chooses whether the indicator is drawn above the price chart (true) or below it.

The colors of the chart settings

To keep the drawing consistent with the chart the user has set up, take the colors from ColorsStore of ChartInfo instead of choosing your own: the up and down candle colors, the background and the others. They are System.Drawing.Color, as are the per-bar colors of a data series (Colors[bar]). A delta histogram in the candle colors of the chart:

using ATAS.Indicators;

namespace ATAS.Docs.Samples.ChartIndicators;

public class DeltaHistogramIndicator : Indicator
{
    public DeltaHistogramIndicator() : base(true)
    {
        Panel = IndicatorDataProvider.NewPanel;
        DataSeries[0].IsHidden = true;
        ((ValueDataSeries)DataSeries[0]).VisualType = VisualMode.Histogram;
    }

    protected override void OnCalculate(int bar, decimal value)
    {
        var candle = GetCandle(bar);
        DataSeries[0][bar] = candle.Delta;

        if (ChartInfo is not null)
        {
            ((ValueDataSeries)DataSeries[0]).Colors[bar] = candle.Delta < 0
                ? ChartInfo.ColorsStore.DownCandleColor
                : ChartInfo.ColorsStore.UpCandleColor;
        }
    }
}

The histogram in the chart colors

ATAS X

Everything drawn through RenderContext works the same in the classic ATAS and in ATAS X. Do not use WPF brushes (System.Windows.Media.SolidColorBrush) or other Windows-only UI types: they are not converted, and ATAS X does not load the assembly (see Project setup). For text, prefer the font of the chart, ChartInfo.PriceAxisFont (a RenderFont) or its FontFamily, so that the text is rendered with a font the platform provides on the current OS; a RenderFont created by name should use a font that exists on both Windows and macOS, such as Arial.