Skip to main content

Chart's Constant Lines - Custom Property

  • 6 minutes to read

This extension draws constant lines for the Chart dashboard item.

In the extension code you can find how to:

  • Add a complex custom property for a specific dashboard item (Chart).
  • Show how to work with complex custom values that are saved as an array.
  • Bind a custom property to a list of data items.
  • Customize export to display the result in the exported document.

Every extension that provides custom property can be divided to the following parts:

Model

The model is a CustomPropertyMetadata object that contains the property name, type, and the default value. It also specifies on which level the property is created (a dashboard, dashboard item or data item container). Use the Model.registerCustomProperty property to register the custom property definition.

var ChartConstantLinesExtension = (function() {
    var Model = DevExpress.Dashboard.Model;

    // 1. Model
    var chartConstantLinesProperty  = {
        ownerType: Model.ChartItem,
        propertyName: 'ConstantLineSettings',
        defaultValue: "[]",
        valueType: 'string'
    };

    Model.registerCustomProperty(chartConstantLinesProperty);

    return ChartConstantLinesExtension;
}());

Viewer

In this part, you modify the viewer according to the saved custom property value. You can use the client methods and events to change the displayed elements. In this example, you change the Chart widget’s argumentAxis.constantLines collection according to the parsed custom property value.

var ChartConstantLinesExtension = (function() {
    var Model = DevExpress.Dashboard.Model;

    // 2. Viewer
    function onItemWidgetOptionsPrepared(args)  {        
        if(args.dashboardItem instanceof Model.ChartItem) {
            var serializedConstantLines = args.dashboardItem.customProperties.getValue(chartConstantLinesProperty.propertyName);
            var constantLines = JSON.parse(serializedConstantLines);

            var valueAxisOptions = args.options["valueAxis"] || [];              
            constantLines.forEach(function(line) {
                var axisOptions = valueAxisOptions[0];
                if(axisOptions) {
                    var value = line.value
                    if(line.isBound) {
                        value = args.itemData.getMeasureValue(line.measureId).getValue()
                    }
                    axisOptions.constantLines = axisOptions.constantLines || [];
                    axisOptions.constantLines.push({
                        value: value, 
                        color: line.color, 
                        dashStyle: 'longDash', 
                        width: 2, 
                        label: { 
                            text: line.labelText
                        }
                    });
                }
            });
        }        
    }

    return ChartConstantLinesExtension;
}());

Designer

This part contains designer settings. Add editors and control elements to configure and change the custom property’s values in the UI. This part is not required if you use the extension in Viewer mode or do not want to let users edit a custom property’s value. In this example, you can configure the constant line settings in the complex editor. It is a pop-up window that displays a list of constant lines and their options (a list of editors such as a check box, input form, color palette, and so on). You can add, remove or edit the existing constant lines in this pop-up window.

var ChartConstantLinesExtension = (function() {
    var Model = DevExpress.Dashboard.Model;
    var dxButton = DevExpress.ui.dxButton;
    var dxPopup = DevExpress.ui.dxPopup;
    var dxForm = DevExpress.ui.dxForm;
    var dxList = DevExpress.ui.dxList;
    var dxToolbar = DevExpress.ui.dxToolbar;
    var DataSource = DevExpress.data.DataSource;

    // 3. Designer
    // Adds a new section.
    function onCustomizeSections(args) {
        var chartItem = args.dashboardItem;
        if(chartItem instanceof Model.ChartItem) {
            args.addSection({
                title: "Constant Lines (custom)",
                items: [
                    {
                        dataField: chartConstantLinesProperty.propertyName,
                        template: function(args, element) { 
                            var buttonContainer = document.createElement('div');
                            new dxButton(buttonContainer, {
                                text: 'Edit',
                                onClick: function() {
                                    showPopup(chartItem)
                                }
                            })
                            return buttonContainer;
                        },
                        label: {
                            visible: false,
                        }
                    }
                ]
            });
        }
    }

    // Generates a pop-up window.
    function showPopup(chartItem) {
        var popupContainer = document.createElement('div');
        document.body.appendChild(popupContainer);
        var popupOptions = { 
            width : '800px',
            height : 'auto',
            closeOnOutsideClick: false,
            contentTemplate: function(contentContainer) {
                var formContainer = document.createElement('div');
                var formOptions = getFormOptions(chartItem);
                this._form = new dxForm(formContainer, formOptions);
                return formContainer;
            },
            onHidden: function() {
                document.body.removeChild(popupContainer)
            },
            title: 'Constant Lines',
        };
        var popup = new dxPopup(popupContainer, popupOptions);
        popup.show();
    }

    function getValue(chartItem) {
        return JSON.parse(chartItem.customProperties.getValue(chartConstantLinesProperty.propertyName))
    }
    function setValue(chartItem, value) {
        return chartItem.customProperties.setValue(chartConstantLinesProperty.propertyName, JSON.stringify(value))
    }

    // Creates editors in the pop-up window.
    function createListAndToolbar(form, chartItem) {
        var element = document.createElement('div');
        var toolbarContainer = document.createElement('div');
        element.appendChild(toolbarContainer);
        var editButton = null;
        var removeButton = null;
        var list = null;

        var toolbarOptions = {
            items: [{
                location: 'before',
                widget: 'dxButton',
                options: {
                    icon: 'add',
                    stylingMode: 'text',
                    onClick: function (e) {
                        var constantLines = getValue(chartItem);
                        var key = constantLines.reduce(function(acc, item) { return acc < item.key ? item.key : acc }, 0) + 1
                        var newConstLine = {
                            key: key,
                            name: 'Constant Line' + key,
                            isBound: false,
                            measureId: '',
                            value: 0,
                            color: '#000000',
                            labelText: ''
                        };
                        form.option('formData', newConstLine);

                        var itemInDataSource = constantLines.filter(function(item) { return item.key === newConstLine.key} )[0];
                        if(!itemInDataSource) {
                            constantLines.push(newConstLine);
                        } else {
                            var index = constantLines.indexOf(itemInDataSource);
                            constantLines[index] = newConstLine;
                        }

                        setValue(chartItem, constantLines);  
                        list.reload();
                        list.option('selectedItem', constantLines[constantLines.length - 1]);

                    },
                }
            },
            {
                location: 'before',
                widget: 'dxButton',
                options: {
                    icon: 'remove',
                    stylingMode: 'text',
                    onInitialized: function (e) { removeButton = e.component },
                    onClick: function() {
                        var constantLines = getValue(chartItem);
                        var selectedKey = list.option('selectedItem').key;
                        var index = constantLines.indexOf(constantLines.filter(function(line) { return line.key === selectedKey })[0]);
                        if(index >= 0) {
                            constantLines.splice(index, 1);
                            setValue(chartItem, constantLines);
                            list.reload();
                            list.option('selectedItem', constantLines[constantLines.length - 1]);
                        }
                    },
                }
            }]
        };

        var updateToolbarState = function (hasSelectedItem) {
            editButton && editButton.option('disabled', !hasSelectedItem);
            removeButton && removeButton.option('disabled', !hasSelectedItem);
        }

        var toolbar = new dxToolbar(toolbarContainer, toolbarOptions);

        var listOptions = {
            dataSource: new DataSource({ load: function() { return getValue(chartItem)} }),
            displayExpr: 'name',
            height: '200px',
            keyExpr: 'key',
            noDataText: 'Add a Constant Line',
            selectionMode: 'single',
            onContentReady: function (e) { updateToolbarState(!!e.component.option('selectedItem')) },
            onSelectionChanged: function (e) {
                updateToolbarState(!!e.component.option('selectedItem'));
                form.option('formData', e.component.option('selectedItem'));    
            } 
        };

        var listContainer = document.createElement('div');
        element.appendChild(listContainer);

        list = new dxList(listContainer, listOptions);

        return element;
    }

    // Gets values for the pop-up window's editors.
    function getFormOptions(chartItem) {
        var updateFormState = function (form) {
            var isBound = form.option('formData')['isBound'];
            var valueEditor = form.getEditor('value');
            valueEditor && valueEditor.option('disabled', isBound);
            var measureIdEditor = form.getEditor('measureId');
            measureIdEditor && measureIdEditor.option('disabled', !isBound);
        };
        return {
            formData: getValue(chartItem)[0] || null,
            colCount: 2,
            items: [
                {
                    itemType: 'group',
                    template : function (args, element) { return createListAndToolbar(args.component, chartItem) },
                }, 
                {
                    itemType: 'group',
                    items : [
                        {
                            dataField: 'name',
                            editorType: 'dxTextBox',
                        }, 
                        {
                            dataField: 'isBound',
                            editorType: "dxCheckBox",
                            label: {
                                text: 'IsBound',
                            },
                        }, 
                        {
                            dataField: 'value',
                            editorType: 'dxNumberBox',
                            label: {
                                text: 'Value',
                            },
                            editorOptions: {
                                showSpinButtons: true,
                            }
                        },
                        {
                            dataField: 'measureId',
                            editorType: 'dxSelectBox',
                            label: {
                                text: 'Hidden measures',
                            },
                            editorOptions: {
                                displayExpr: 'text',
                                valueExpr: 'value',
                                items:  chartItem.hiddenMeasures().map(function(measure) { 
                                    return {
                                        text: measure.name() || measure.dataMember(),
                                        value: measure.uniqueName()
                                    }
                                }),
                            }
                        },
                        {
                            dataField: 'color',
                            editorType: 'dxColorBox',
                            label: {
                                text: 'Color',
                            }
                        },
                        {
                            dataField: 'labelText',
                            editorType: 'dxTextBox',
                        }
                    ]
                }
            ],
            onContentReady: function(e) { updateFormState(e.component) },
            onFieldDataChanged: function(e) {
                var formData = e.component.option("formData");
                var constantLines = getValue(chartItem);
                var editedConstantLine = constantLines.filter(function(line) { return line.key === formData.key })[0];
                constantLines[constantLines.indexOf(editedConstantLine)] = formData;
                setValue(chartItem, constantLines);  
                updateFormState(e.component) 
            },
        };
    }

    return ChartConstantLinesExtension;
}());

Event Subscription

This part contains event subscriptions. In this example, it is the ViewerApiExtensionOptions.onItemWidgetOptionsPrepared and OptionsPanelExtensionOptions.onCustomizeSections events.

var ChartConstantLinesExtension = (function() {

    // 4. Event Subscription
    function ChartConstantLinesExtension(dashboardControl) {
        this.name = 'ChartConstantLines';
        this.start = function() {
            var viewerApiExtension = dashboardControl.findExtension('viewerApi');
            if(viewerApiExtension) {
                viewerApiExtension.on('itemWidgetOptionsPrepared', onItemWidgetOptionsPrepared)
            }
            var optionsPanelExtension = dashboardControl.findExtension("itemOptionsPanel")
            if(optionsPanelExtension) {
                optionsPanelExtension.on('customizeSections', onCustomizeSections)
            }
        }
        this.stop = function (){
            var viewerApiExtension = dashboardControl.findExtension('viewerApi');
            if(viewerApiExtension) {
                viewerApiExtension.off('itemWidgetOptionsPrepared', onItemWidgetOptionsPrepared)
            }
            var optionsPanelExtension = dashboardControl.findExtension("itemOptionsPanel")
            if(optionsPanelExtension) {
                optionsPanelExtension.off('customizeSections', onCustomizeSections)
            }
        }
    }

    return ChartConstantLinesExtension;
}());

Tip

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