Chart Designer for End Users

  • 14 minutes to read

The Chart Designer allows users to customize the chart's appearance. This topic explains how to invoke and modify the Chart Designer.

ChartDesigner_ItemSelection

This article consists of the following sections:

Invoke the Chart Designer

Create a ChartDesigner class instance and pass a ChartControl that should be customized to the ChartDesigner constructor parameters. Use the ChartDesigner.ShowDialog method to invoke the designer.

ChartDesigner designer = new ChartDesigner(chartControl);
designer.ShowDialog();

Customize the Chart Designer

The following API members allow you to customize the Chart Designer:

ChartDesigner designer = new ChartDesigner(chartControl1);
designer.Icon = new System.Drawing.Icon("../../../Icon.ico", new System.Drawing.Size(32,32));
designer.ShowIcon = true;
designer.AddElementMenuOptions.ShowAddSeriesMenuItem = false;
designer.AddElementMenuOptions.ShowAddSeriesTitleMenuItem = false;
designer.Caption = "Chart Designer";
designer.EnableLargeDataSetWarning = true;
designer.AvailableViewTypes.Clear();
designer.AvailableViewTypes.Add(new ViewTypeGroup("Available Series", new List<ViewType> { ViewType.Area, ViewType.Spline, ViewType.Bar }));

designer.ChartElementHighlighting += OnChartElementHighlighting;
designer.ChartElementSelecting += OnChartElementSelecting;
designer.ChartStructureUpdating += OnChartStructureUpdating;
designer.PropertyDescriptorsCustomizing += OnDesignerPropertyDescriptorsCustomizing;

designer.ShowDialog();

// Handlers for these events are shown below:

private void OnChartStructureUpdating(object sender, ChartStructureChangingEventArgs e) {
    e.ChartModel.Series.AllowAddChild = false;
    foreach(SeriesModel seriesModel in e.ChartModel.Series) { 
        seriesModel.AllowChangeVisibility = false;
        seriesModel.AllowRemove = false;
    }
    e.ChartModel.Legends.ShowInStructureControl = false;
}

private void OnChartElementSelecting(object sender, ChartElementSelectingEventArgs e) {
    if(e.ElementModel is SeriesBaseModel) {
        e.Cancel = false;
        return;
    }
    if (e.ElementModel is LegendModel) {
        e.ShowPropertiesTab = false;
        e.ShowDataTab = false;
        e.CustomOptionsControl = new CustomLegendOptionsControl(e.ElementModel); 
    }
}

private void OnChartElementHighlighting(object sender, ChartElementHighlightingEventArgs e) {
    SeriesBaseModel series = e.ElementModel as SeriesBaseModel;
    if(series == null) {
        e.Cancel = true;
        return;
    }

}

private void OnDesignerPropertyDescriptorsCustomizing(object sender, PropertyDescriptorsCustomizingEventArgs e) {
    if (e.ElementModel is ChartModel) {
        e.Properties.Remove(e.Properties["BackColor"]);
    }
}

You can use the ChartDesigner.ChartElementSelecting event to replace the control used in the Options tab. The custom control should inherit the DevExpress.XtraCharts.Designer.CustomOptionsControl class. The image below shows a custom Legend Options tab.

ChartDesigner-CustomOptionsTab

The following code demonstrates how to design the Legend Options tab as shown in the previous image:

using DevExpress.Utils;
using DevExpress.XtraCharts;
using DevExpress.XtraCharts.Designer;
using System;
using System.Windows.Forms;

namespace CrosshairOptions {
    partial class CustomLegendOptionsControl : CustomOptionsControl {
        bool updateStarted = false;

        LegendModel LegendModel { get { return (LegendModel)this.Model; } }

        public CustomLegendOptionsControl(ChartElementModel model) : base(model) {
            InitializeComponent();
            if(!(model is LegendModel)) throw new ArgumentException("The model must have the LegendModel type.");
        }

        protected override void OnLoad(EventArgs e) {
            base.OnLoad(e);

            var horizontalAlignments = Enum.GetValues(typeof(LegendAlignmentHorizontal));
            cbeHorizontalAlignment.Properties.Items.AddRange(horizontalAlignments);
            var verticvalAlignments = Enum.GetValues(typeof(LegendAlignmentVertical));
            cbeVerticalAlignment.Properties.Items.AddRange(verticvalAlignments);

            UpdateView();
        }

        public override void OnModelUpdated() {
            UpdateView();
        }

        protected void UpdateView() {
            updateStarted = true;
            cbeHorizontalAlignment.SelectedItem = LegendModel.AlignmentHorizontal;
            cbeVerticalAlignment.SelectedItem = LegendModel.AlignmentVertical;
            ceVisible.CheckState = DefaultBooleanToCheckState(LegendModel.Visibility);
            updateStarted = false;
        }

        protected void OnVerticalAlignmentChanged(object sender, EventArgs args) {
            if(!updateStarted)
                LegendModel.AlignmentVertical = (LegendAlignmentVertical)cbeVerticalAlignment.SelectedItem;
        }

        protected void OnHorizontalAlignmentChanged(object sender, EventArgs args) {
            if(!updateStarted)
                LegendModel.AlignmentHorizontal = (LegendAlignmentHorizontal)cbeHorizontalAlignment.SelectedItem;
        }

        protected void OnVisibilityChanged(object sender, EventArgs args) {
            if(!updateStarted)
                LegendModel.Visibility = CheckStateToDefaultBoolean(ceVisible.CheckState);
        }

        protected static CheckState DefaultBooleanToCheckState(DefaultBoolean b) {
            switch(b) {
                case DefaultBoolean.Default: return CheckState.Indeterminate;
                case DefaultBoolean.False: return CheckState.Unchecked;
                case DefaultBoolean.True: return CheckState.Checked;
                default: throw new Exception("The specified DefaultBoolean value is not supported.");
            }
        }

        protected static DefaultBoolean CheckStateToDefaultBoolean(CheckState b) {
            switch(b) {
                case CheckState.Indeterminate: return DefaultBoolean.Default;
                case CheckState.Unchecked: return DefaultBoolean.False;
                case CheckState.Checked: return DefaultBoolean.True;
                default: throw new Exception("The specified CheckState value is not supported.");
            }
        }
    }
}

Create a Model for a Custom Chart Element

Users configure chart element model properties when they modify chart element properties in the Chart Designer. Model changes apply to a corresponding chart element after the user clicks OK. Models are provided for existing chart elements. If you create a custom element (the colorizer in the example below), you should create and register a model for this element to allow users to edit its options in the Chart Designer.

Example

This example illustrates how to create and register a model (the CustomPointColorizerModel class in this example) for a custom colorizer (the CustomPointColorizer class in this example).

NOTE

Visit the How to Create a Model for a Custom Chart Element GitHub page to review and download a complete sample project.

Step 1. Create a Model

For example, you have a custom colorizer CustomPointColorizer class that looks as follows:

[TypeConverter(typeof(ExpandableObjectConverter))]
public class CustomPointColorizer : ChartColorizerBase {
    double threshold = 60;
    Color lower = Color.Red;
    Color upper = Color.Green;

    public double Value {
        get { return threshold; }
        set { threshold = value; }
    }
    public Color LowerValuePointColor {
        get { return lower; }
        set { lower = value; }
    }
    public Color UpperValuePointColor {
        get { return upper; }
        set { upper = value; }
    }
    public override Color GetAggregatedPointColor(object argument, object[] values, SeriesPoint[] points, Palette palette) {
        if ((double)values[0] > Value)
            return UpperValuePointColor;
        else
            return LowerValuePointColor;
    }
    public override Color GetPointColor(object argument, object[] values, object colorKey, Palette palette) {
        return Color.Empty;
    }
    protected override ChartElement CreateObjectForClone() {
        return new CustomPointColorizer();
    }
    public override void Assign(ChartElement obj) {
        base.Assign(obj);
        CustomPointColorizer colorizer = obj as CustomPointColorizer;
        if (colorizer != null) {
            Value = colorizer.Value;
            LowerValuePointColor = colorizer.LowerValuePointColor;
            UpperValuePointColor = colorizer.UpperValuePointColor;
        }
    }
    public override string ToString() {
        return "(CustomPointColorizer)";
    }
}

Next, create a CustomPointColorizerModel model for your colorizer. This model is used to modify the colorizer options in the Chart Designer's Properties tab.

public class CustomPointColorizerModel : ChartColorizerBaseModel {
    CustomPointColorizer MyColorizer { get { return (CustomPointColorizer)Colorizer; } } 

    public double Value {
        get { return MyColorizer.Value; }
        set { SetProperty("Value", value); }
    }
    public Color LowerValuePointColor {
        get { return MyColorizer.LowerValuePointColor; }
        set { SetProperty("LowerValuePointColor", value); }
    }
    public Color UpperValuePointColor {
        get { return MyColorizer.UpperValuePointColor; }
        set { SetProperty("UpperValuePointColor", value); }
    }
    public CustomPointColorizerModel(ChartColorizerBase element, CustomModelProvider customModelProvider) : base(element, customModelProvider) {
    }
}

Step 2. Customize the Property Editor

Use the Editor attribute to configure an editor for a Chart Designer property. In this example, you add the CustomPointColorizer item to the list of colorizers for the bar series model's Colorizer property.

public class SideBySideBarSeriesViewCustomModel : SideBySideBarSeriesViewModel {
    [Editor(typeof(CustomColorizerEditor), typeof(UITypeEditor))]
    public new ChartColorizerBaseModel Colorizer {
        get { return base.Colorizer; }
        set { base.Colorizer = value; }
    }

    public SideBySideBarSeriesViewCustomModel(SideBySideBarSeriesView element, CustomModelProvider customModelProvider)
        : base(element, customModelProvider) {
    }
}

public class CustomColorizerEditor : UITypeEditor {
    IWindowsFormsEditorService editorService;
    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
        editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
        var seriesView = context.Instance as SeriesViewBaseModel;

        List<ChartColorizerBase> colorizers = GetColorizers();

        CustomModelProvider modelProvider = new CustomModelProvider();
        modelProvider.RegisterCustomModelType(typeof(CustomPointColorizer), typeof(CustomPointColorizerModel));

        var listBox = new ListBoxControl();
        listBox.Click += listBox_Click;
        listBox.Items.Add("(None)");
        foreach (ChartColorizerBase colorizer in colorizers) {
            var colorizerModel = ModelHelper.GetModel<ChartColorizerBaseModel>(colorizer, modelProvider);
            int index = listBox.Items.Add(colorizerModel);
            if (value != null && colorizerModel.GetType() == value.GetType()) {
                listBox.SelectedIndex = index;
            }
        }
        editorService.DropDownControl(listBox);
        if (listBox.SelectedIndex != 0)
            if (value == null || listBox.SelectedItem.GetType() != value.GetType())
                return listBox.SelectedItem;
            else
                return value;
        else
            return null;
    }
    void listBox_Click(object sender, EventArgs e) {
        this.editorService.CloseDropDown();
    }
    List<ChartColorizerBase> GetColorizers() {
        return new List<ChartColorizerBase>() { new CustomPointColorizer(), new KeyColorColorizer(), new RangeColorizer() };
    }
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
        return UITypeEditorEditStyle.DropDown;
    }
}

Step 3. Register Models

To associate the model with the chart element, use the ChartDesigner.RegisterCustomModelType(System.Type,System.Type) method.

void OnButtonClick(object sender, EventArgs e) {
    ChartDesigner chartDesigner = new ChartDesigner((ChartControl)this.Controls["MyChart"]);
    chartDesigner.RegisterCustomModelType(typeof(CustomPointColorizer), typeof(CustomPointColorizerModel));
    chartDesigner.RegisterCustomModelType(typeof(SideBySideBarSeriesView), typeof(SideBySideBarSeriesViewCustomModel));
    chartDesigner.ShowDialog(true);
}

Localize the Chart Designer

Use one of the following approaches to localize text used in the Chart Designer:

  1. Use Satellite Resource Assemblies (standard localization mechanism).

    In this approach, use DevExpress Localization Service to modify or create resource assemblies. To access translations specific to the Chart Designer in the Localization Service, set .NET PLATFORM to WinForms and use one of the following modules:

    • XtraCharts.Wizard - Contains resource strings for the Chart Designer's UI elements, captions, notification messages, and option tabs.
    • XtraCharts - Contains resource strings for property names displayed in the Properties tabs.

    See Localizing WinForms Controls via Satellite Resource Assemblies for more information.

  2. Use Localizer Objects.

    • Create a class that inherits the ChartLocalizer class.
    • Override the GetLocalizedString method to return a translation for a resource string with the specified ID. The ChartStringId enum stores all identifiers for strings the Chart Designer uses.
    • Initialize the static ChartLocalizer.Active property with the newly created class.
    public Form1() {
        ChartLocalizer.Active = new GermanChartDesignerLocalizer();
        InitializeComponent();
    
    }
    
    public class GermanChartDesignerLocalizer: ChartLocalizer {
    public override string Language { get { return "Deutsch"; } }
    public override string GetLocalizedString(ChartStringId id) {
        string res = "";
        switch (id) {
            // ... 
            case ChartStringId.SvnArea: return "Fläche";
            case ChartStringId.SvnSideBySideBar: return "Säulen";
            case ChartStringId.SvnSpline: return "Keil";
            // ... 
            default:
                res = base.GetLocalizedString(id);
                break;
        }
        return res;
    }
    

    See Localizing WinForms Controls via Localizer Objects for more information.