Add custom series graphics to area chart

This post shows how to add custom graphics to a chart by inheriting the SeriesRenderer class. We’ll use it to show marker lines on top of an area chart, which could be used to emphasize important values (e.g. dates on a timeline).

Marker lines can be drawn by overriding Renderer2D.Draw method:

class MarkerLineRenderer : Renderer2D
{
    public MarkerLineRenderer(SimpleSeries series) :
        base(new ObservableCollection<Series> { series })
    {
    }

    protected override void Draw(RenderContext context)
    {
        var series = Series[0];
        using (var pen = new Pen(Color.Red, 2)
            { DashStyle = System.Drawing.Drawing2D.DashStyle.Dot })
        {
            for (int i = 0; i < series.Size; i++)
            {
                double x = series.GetValue(i, 0);
                var point = GetPixel(
                    x, context.XAxis,
                    0, context.YAxis, context.Component);
                context.Graphics.DrawLine(
                    pen, point.X, 0,
                    point.X, (float)context.Component.ActualHeight);
            }
        }
    }
}

We will provide marker coordinates by populating a SimpleSeries instance:

var markerCoords = new SimpleSeries(
    new double[] { 5, 20, 23 }, null);

and add the new graphics on top of area chart ones:

areaChart.Plot.SeriesRenderers.Add(
    new MarkerLineRenderer(markerCoords));

By changing DrawLines calls above with DrawImage, you could render icons on top of the chart instead. Alternatively, draw custom geometries by calling DrawPath method.

Similar approach could be used to draw crosshairs at mouse position:

class CrossHairsRenderer : Renderer2D
{
    public CrossHairsRenderer(
        Chart control, ObservableCollection<Series> series) : base(series)
    {
        this.control = control;
    }

    protected override void Draw(RenderContext context)
    {
        var chartPos = control.PointToClient(Cursor.Position);
        var plotPos = context.Component.RootToLocal(
            new PointD(chartPos.X, chartPos.Y));
        using (var pen = new Pen(Color.Black, 2)
            { DashStyle = System.Drawing.Drawing2D.DashStyle.Dot })
        {
            context.Graphics.DrawLine(
                pen, (int)plotPos.X, 0,
                (int)plotPos.X, (int)context.Component.ActualHeight);
            context.Graphics.DrawLine(
                pen, 0, (int)plotPos.Y,
                (int)context.Component.ActualWidth, (int)plotPos.Y);
        }
    }

    Chart control;
}

That would also require repainting the chart from MouseMove event handler:

areaChart.Plot.SeriesRenderers.Add(
    new CrossHairsRenderer(areaChart, areaChart.Series));
areaChart.MouseMove += OnChartMouseMove;

void OnChartMouseMove(object? sender, MouseEventArgs e)
{
    areaChart.Invalidate();
}

Crosshairs above do not access chart data and could be implemented as a custom Charting.Component object instead, overlaying it on top of the plot in chart’s layout. However the SeriesRender allows for easier integration with the plot, and for possible extensions to draw nearby values or coordinates from the series at the mouse location.

And here’s a complete sample projects that shows both custom renderers together:
https://mindfusion.eu/_samples/ChartCrossHairs.zip

Sample code above demonstrates MindFusion.Charting library for .NET Windows Forms, however we provide same API for Java, JavaScript and other .NET platforms such as Blazor and MAUI.

Enjoy!