Skip to main content

Chart Item - Constant Line

  • 14 minutes to read

This topic describes how to create a custom property for a dashboard item. In this example, the property provides editors that allow you to draw a constant line for the selected Chart item in the WinForms Dashboard Designer. The ChartContext.GetDashboardItemSeries method is used to provide a connection between data item containers from the Values section and the series from an underlying Chart control.

View Example: WinForms Dashboard - Custom Properties

Create the Custom Functionality Module

The code is organized into a separate module you can integrate into any dashboard application.

Create the ConstantLineUserValueModule class that serves as a custom functionality module and contains:

  • a dashboard control that you pass as a parameter when you register the module,
  • a custom property’s unique name,
  • event subscriptions that are used to provide custom functionality,
  • a ribbon in which you add a button to edit custom property’s value.

Note

You can use the IDashboardControl interface that provides common API for WinForms Designer and Viewer to write code that can be used in both controls simultaneously.

You can add control elements to the Ribbon / Toolbar to change the custom property’s value in the UI.

In this example, the AddBarItem method places the Constant Line button in the Custom Properties ribbon group on the Chart’s Design page.

public class ConstantLineUserValueModule {
public static readonly string PropertyName = "ConstantLine";
readonly DashboardDesigner designer;

public ConstantLineUserValueModule(DashboardDesigner designer, SvgImage barImage = null) {
    this.designer = designer;
    BarButtonItem barItem = AddBarItem("Constant Line", barImage, designer.Ribbon);
    barItem.ItemClick += BarItem_Click;
    designer.DashboardItemControlUpdated += Designer_DashboardItemControlUpdated;
}

BarButtonItem AddBarItem(string caption, SvgImage barImage, RibbonControl ribbon) {
    var page = ribbon.GetDashboardRibbonPage(DashboardBarItemCategory.ChartTools, DashboardRibbonPage.Design);
    RibbonPageGroup group = page.GetGroupByName("Custom Properties");
    if(group == null) {
        group = new RibbonPageGroup("Custom Properties") { Name = "Custom Properties" };
        page.Groups.Add(group);
    }
    var barItem = new BarButtonItem(ribbon.Manager, caption);
    barItem.ImageOptions.SvgImage = barImage;
    group.ItemLinks.Add(barItem);
    return barItem;
}

Create a Class for Data Conversion

Create the ConstantLineUserValueModuleData class that parses the selected chart item’s data.

The GetDataFromString method returns the ConstantLineUserValueModuleData class instance that contains parsed values.

The GetStringFromData method returns an instance’s values as a string: {Pane 1_False_100000}.

  • “Pane 1” is the PaneName string value.
  • “False” is the IsSecondaryAxis Boolean value that defines whether to create a constant line on the secondary Y axis.
  • “100000” is the Value numeric value that is the position of the constant line on the Y (or secondary Y) axis.
public class ConstantLineUserValueModuleData {
    public string PaneName { get; set; }
    public bool IsSecondaryAxis { get; set; }
    public double Value { get; set; }

    public string GetStringFromData() {
        return PaneName + "_" + IsSecondaryAxis.ToString() + "_" + Value;
    }
}
ConstantLineUserValueModuleData GetDataFromString(string customPropertyValue) {
    if(!string.IsNullOrEmpty(customPropertyValue)) {
        var array = customPropertyValue.Split('_');
        return new ConstantLineUserValueModuleData() {
            PaneName = array[0],
            IsSecondaryAxis = bool.Parse(array[1]),
            Value = Convert.ToDouble(array[2])
        };
    }
    return new ConstantLineUserValueModuleData();
}

Create Editors

Create a new form that allows users to edit the constant line’s options.

Inherit the XtraUserControl. In this example, it is the ValueSelectorControl class. Add the DataLayoutControl. Next, add editors to the control that allows you to edit options.

public class ValueSelectorControl : XtraUserControl {
    public ConstantLineUserValueModuleData ConstantLineModuleData { get; }

        public ValueSelectorControl(List<string> paneNames) {
            DataLayoutControl dataLayoutControl = new DataLayoutControl();
            BindingSource source = new BindingSource();
            ConstantLineModuleData = new ConstantLineUserValueModuleData();
            source.DataSource = ConstantLineModuleData;
            dataLayoutControl.DataSource = source;
            dataLayoutControl.FieldRetrieving += (s, e) => {
                if(e.FieldName == nameof(ConstantLineModuleData.PaneName)) {
                    e.EditorType = typeof(LookUpEdit);
                    e.Handled = true;
                }
            };
            dataLayoutControl.FieldRetrieved += (s, e) => {
                if(e.FieldName == nameof(ConstantLineModuleData.PaneName))
                    InitRepositoryItem(e.RepositoryItem, paneNames);
            };
            dataLayoutControl.RetrieveFields();
            dataLayoutControl.Dock = DockStyle.Fill;
            Controls.Add(dataLayoutControl);
            Dock = DockStyle.Top;
        }
        void InitRepositoryItem<T>(RepositoryItem ri, List<T> list) {
            var lookUpEdit = ri as RepositoryItemLookUpEdit;
            lookUpEdit.TextEditStyle = TextEditStyles.DisableTextEditor;
            lookUpEdit.AllowNullInput = DefaultBoolean.False;
            lookUpEdit.DataSource = list;
        }
    }
}

Use the GetConstantLineModuleData method to get the specified options.

ConstantLineUserValueModuleData GetConstantLineModuleData(ChartDashboardItem dashboardItem) {
    using(ValueSelectorControl selector = new ValueSelectorControl(dashboardItem.Panes.Select(p => p.Name).ToList())) {
        if(XtraDialog.Show(selector, "Select the required series:") == DialogResult.OK) {
            return selector.ConstantLineModuleData;
        }
    }
    return null;
}

The following image shows the ValueSelectorControl:

Save the Custom Property’s Value to a Dashboard

In this example, the custom property’s value is stored in the DashboardItem.CustomProperties collection.

Use the DashboardDesigner.AddToHistory method to record a new custom property’s value and save the action to the Dashboard Designer’s history when a user changes the value. This method calls the CustomProperties.SetValue method and adds the information to the history item. You can undo/redo this action like other user actions.

void BarItem_Click(object sender, ItemClickEventArgs e) {
    var chartItem = designer.SelectedDashboardItem as ChartDashboardItem;
    if(chartItem != null) {
        var data = GetConstantLineModuleData(chartItem);
        if(data != null)
            designer.AddToHistory(new CustomPropertyHistoryItem(chartItem, PropertyName, data.GetStringFromData(), $"{data.PaneName} constant line is set"));
    }
}

Apply the Custom Property’s Value to the Underlying Control

Create the UpdateChart method to draw a constant line. Create a new ConstantLine class instance and assign AxisValue as the constant line value to define the constant line’s position.

Handle the DashboardDesigner.DashboardItemControlUpdated event to update the underlying Chart. Use the CustomProperties.GetValue method to get the custom property’s value and update the control’s options according to this value.

void UpdateChart(ChartDashboardItem chartDashboardItem, ChartContext chartContext) {
    var moduleData = GetDataFromString(chartDashboardItem.CustomProperties.GetValue(PropertyName));
    var pane = chartDashboardItem.Panes.FirstOrDefault(x => x.Name == moduleData.PaneName);
    if(pane != null) {
        ChartSeries dashboardSeries = pane.Series.FirstOrDefault(s => s.PlotOnSecondaryAxis == moduleData.IsSecondaryAxis);
        if(dashboardSeries != null) {
            Series chartSeries = chartContext.GetControlSeries(dashboardSeries).FirstOrDefault();
            var chartAxis = (chartSeries.View as XYDiagramSeriesViewBase)?.AxisY;
            if(chartAxis != null) {
                ConstantLine line = new ConstantLine() { AxisValue = moduleData.Value };
                chartAxis.ConstantLines.Clear();
                chartAxis.ConstantLines.Add(line);
                line.ShowInLegend = false;
                line.Color = Color.Green;
                line.LineStyle.Thickness = 2;
                line.LineStyle.DashStyle = DashStyle.Dash;
                line.Title.Text = "Value: " + moduleData.Value.ToString();
                line.Title.TextColor = line.Color;
            }
        }
    }
}
void Designer_DashboardItemControlUpdated(object sender, DashboardItemControlEventArgs e) {
    if(e.ChartControl != null && designer.Dashboard.Items[e.DashboardItemName] is ChartDashboardItem chartDashboardItem)
        UpdateChart(chartDashboardItem, e.ChartContext);
}

Register the Custom Functionality Module

The code below is a complete module you need to add the Chart item’s Constant Line option:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using DevExpress.DashboardCommon;
using DevExpress.DashboardWin;
using DevExpress.Utils;
using DevExpress.Utils.Svg;
using DevExpress.XtraBars;
using DevExpress.XtraBars.Ribbon;
using DevExpress.XtraCharts;
using DevExpress.XtraDataLayout;
using DevExpress.XtraEditors;
using DevExpress.XtraEditors.Controls;
using DevExpress.XtraEditors.Repository;

namespace WindowsFormsAppCustomProperties {
    public class ConstantLineUserValueModuleData {
        public string PaneName { get; set; }
        public bool IsSecondaryAxis { get; set; }
        public double Value { get; set; }
        public string GetStringFromData() {
            return PaneName + "_" + IsSecondaryAxis.ToString() + "_" + Value;
        }
    }
    public class ConstantLineUserValueModule {
        public static readonly string PropertyName = "ConstantLine";
        readonly DashboardDesigner designer;
        public ConstantLineUserValueModule(DashboardDesigner designer, SvgImage barImage = null) {
            this.designer = designer;
            BarButtonItem barItem = AddBarItem("Constant line", barImage, designer.Ribbon);
            barItem.ItemClick += BarItem_Click;
            designer.DashboardItemControlUpdated += Designer_DashboardItemControlUpdated;
        }
        BarButtonItem AddBarItem(string caption, SvgImage barImage, RibbonControl ribbon) {
            var page = ribbon.GetDashboardRibbonPage(DashboardBarItemCategory.ChartTools, DashboardRibbonPage.Design);
            RibbonPageGroup group = page.GetGroupByName("Custom Properties");
            if(group == null) {
                group = new RibbonPageGroup("Custom Properties") { Name = "Custom Properties" };
                page.Groups.Add(group);
            }
            var barItem = new BarButtonItem(ribbon.Manager, caption);
            barItem.ImageOptions.SvgImage = barImage;
            group.ItemLinks.Add(barItem);
            return barItem;
        }
        void BarItem_Click(object sender, ItemClickEventArgs e) {
            var chartItem = designer.SelectedDashboardItem as ChartDashboardItem;
            if(chartItem != null) {
                var data = GetConstantLineModuleData(chartItem);
                if(data != null)
                    designer.AddToHistory(new CustomPropertyHistoryItem(chartItem, PropertyName, data.GetStringFromData(), $"{data.PaneName} constant line is set"));
            }
        }
        ConstantLineUserValueModuleData GetConstantLineModuleData(ChartDashboardItem dashboardItem) {
            using(ValueSelectorControl selector = new ValueSelectorControl(dashboardItem.Panes.Select(p => p.Name).ToList())) {
                if(XtraDialog.Show(selector, "Select the required series") == DialogResult.OK) {
                    return selector.ConstantLineModuleData;
                }
            }
            return null;
        }
        void Designer_DashboardItemControlUpdated(object sender, DashboardItemControlEventArgs e) {
            if(e.ChartControl != null && designer.Dashboard.Items[e.DashboardItemName] is ChartDashboardItem chartDashboardItem)
                UpdateChart(chartDashboardItem, e.ChartContext);
        }
        void UpdateChart(ChartDashboardItem chartDashboardItem, ChartContext chartContext) {
            var moduleData = GetDataFromString(chartDashboardItem.CustomProperties.GetValue(PropertyName));
            var pane = chartDashboardItem.Panes.FirstOrDefault(x => x.Name == moduleData.PaneName);
            if(pane != null) {
                ChartSeries dashboardSeries = pane.Series.FirstOrDefault(s => s.PlotOnSecondaryAxis == moduleData.IsSecondaryAxis);
                if(dashboardSeries != null) {
                    Series chartSeries = chartContext.GetControlSeries(dashboardSeries).FirstOrDefault();
                    var chartAxis = (chartSeries.View as XYDiagramSeriesViewBase)?.AxisY;
                    if(chartAxis != null) {
                        ConstantLine line = new ConstantLine() { AxisValue = moduleData.Value };
                        chartAxis.ConstantLines.Clear();
                        chartAxis.ConstantLines.Add(line);
                        line.ShowInLegend = false;
                        line.Color = Color.Green;
                        line.LineStyle.Thickness = 2;
                        line.LineStyle.DashStyle = DashStyle.Dash;
                        line.Title.Text = "Value: " + moduleData.Value.ToString();
                        line.Title.TextColor = line.Color;
                    }
                }
            }
        }
        ConstantLineUserValueModuleData GetDataFromString(string customPropertyValue) {
            if(!string.IsNullOrEmpty(customPropertyValue)) {
                var array = customPropertyValue.Split('_');
                return new ConstantLineUserValueModuleData() {
                    PaneName = array[0],
                    IsSecondaryAxis = bool.Parse(array[1]),
                    Value = Convert.ToDouble(array[2])
                };
            }
            return new ConstantLineUserValueModuleData();
        }
    }
    public class ValueSelectorControl: XtraUserControl {
        public ConstantLineUserValueModuleData ConstantLineModuleData { get; }
        public ValueSelectorControl(List<string> paneNames) {
            DataLayoutControl dataLayoutControl = new DataLayoutControl();
            BindingSource source = new BindingSource();
            ConstantLineModuleData = new ConstantLineUserValueModuleData();
            source.DataSource = ConstantLineModuleData;
            dataLayoutControl.DataSource = source;
            dataLayoutControl.FieldRetrieving += (s, e) => {
                if(e.FieldName == nameof(ConstantLineModuleData.PaneName)) {
                    e.EditorType = typeof(LookUpEdit);
                    e.Handled = true;
                }
            };
            dataLayoutControl.FieldRetrieved += (s, e) => {
                if(e.FieldName == nameof(ConstantLineModuleData.PaneName))
                    InitRepositoryItem(e.RepositoryItem, paneNames);
            };
            dataLayoutControl.RetrieveFields();
            dataLayoutControl.Dock = DockStyle.Fill;
            Controls.Add(dataLayoutControl);
            Dock = DockStyle.Top;
        }
        void InitRepositoryItem<T>(RepositoryItem ri, List<T> list) {
            var lookUpEdit = ri as RepositoryItemLookUpEdit;
            lookUpEdit.TextEditStyle = TextEditStyles.DisableTextEditor;
            lookUpEdit.AllowNullInput = DefaultBoolean.False;
            lookUpEdit.DataSource = list;
        }
    }
}

Register it before you load a dashboard to apply settings to this dashboard. For this, create a new ConstantLineUserValueModule instance and pass the Dashboard Designer for which you register a custom property. You can create the SvgImageCollection instance to store vector images and use one of them as the bar item’s icon.

using WindowsFormsAppCustomProperties;
//...
public Form1() {
    InitializeComponent();
    new ConstantLineUserValueModule(dashboardDesigner1, svgImageCollection1["chartrangearea"]);
    dashboardDesigner1.LoadDashboard("../../Dashboard/newDashboard.xml");
//...
}

Result

After you register the ChartScaleBreakModule module, the ConstantLine button is added that draws the constant line for the selected Chart item in a dashboard. Clicking the button invokes the ValueSelectorControl editor that allows users to specify the pane, axis, and constant line’s position.

You can undo/redo actions, because information about the scale break property’s state is saved in the history.

Tip

You can also download the multiplatform example on GitHub: Constant Lines

See Also