Example T1051592
Visible to All Users

Dashboard for React - Custom Item Gallery

The example contains the source code of the most requested custom items you can use in your Web Dashboard application. Use the custom items from this example as they are, or modify them according to your needs. In this Web Dashboard application, you can add custom items from the Custom Items group in the Toolbox:

toolbox

This example uses a client-server architecture. The server (backend) project communicates with the client (frontend) application that includes all the necessary styles, scripts and HTML templates. Note that the script version on the client must match the version of libraries on the server.

Files to Review

Quick Start

Server

In the asp-net-core-server folder, run the following command:

Code
dotnet run

The server starts at http://localhost:5000 and the client gets data from http://localhost:5000/api/dashboard. To debug the server, run the asp-net-core-server application in Visual Studio and change the client's endpoint property according to the listening port: https://localhost:44374/api/dashboard.

See the following section for information on how to install NuGet packages from the DevExpress NuGet feed: Install DevExpress Controls Using NuGet Packages.

This server allows CORS requests from all origins with any scheme (http or https). This default configuration is insecure: any website can make cross-origin requests to the app. We recommend that you specify the client application's URL to prohibit other clients from accessing sensitive information stored on the server. Learn more: Cross-Origin Resource Sharing (CORS)

Client

In the dashboard-react-app folder, run the following commands:

Code
npm install npm start

Open http://localhost:3000/ in your browser to see the result.

Country Sales Dashboard

The dashboard displays product sales for the selected category. Use the Country parameter to filter data by country. Select a category on the Polar Chart to show sales by products from this category in the table.

This dashboard contains the following custom items:

Simple Table

View Script: SimpleTableItem.js

A custom Simple Table item renders data from the measure / dimensions as an HTML table. You can use the Simple Table as a detail item along with the Master-Filtering feature. This custom item supports the following settings that you can configure in the Web Dashboard UI:

simple-table-item

  • Show Headers - Specifies whether to show the field headers in the table. The default value is Auto.
  • Text Color - Allows you to change the text color. The default value is Dark.

Funnel D3 Chart Item

View Script: FunnelD3Item.js

A custom Funnel D3 Chart item renders a funnel chart using the D3Funnel JS library. This custom item supports the following settings that you can configure in the Web Dashboard UI:

funnel-d3-item

  • Fill Type - Specifies the funnel chart's solid or gradient fill type.
  • Curved - Specifies whether the funnel is curved.
  • Dynamic Height - Specifies whether the height of blocks are proportional to their weight.
  • Pinch Count - Specifies how many blocks to pinch at the bottom to create a funnel "neck".

Polar Chart Item

View Script: PolarChartItem.js

A custom Polar Chart item that allows you to use the dxPolarChart DevExtreme widget in your dashboards. This item supports the following settings that you can configure in the Web Dashboard UI:

polar-chart-item

  • Display Labels - Specifies whether to show point labels.

Parameter Item

View Script: ParameterItem.js

A custom Parameter item renders dashboard parameter dialog content inside the dashboard layout, and allows you to edit and submit parameter values. This item supports the following settings that you can configure in the Web Dashboard UI:

parameter-item

  • Show Headers - Specifies whether to show headers in the parameters table.
  • Show Parameter Name - Specifies whether to show the first column with parameter names.
  • Automatic Updates - Specifies whether a parameter item is updated automatically. When enabled, this option hides the 'Submit' and 'Reset' buttons.

Country Info Dashboard

The dashboard displays information from Wikipedia for the selected country.

This dashboard contains the following custom items:

Online Map

View Script: OnlineMapItem.js

A custom Online Map item allows you to place callouts on Google or Bing maps using geographical coordinates. The dxMap is used as an underlying UI component. This custom item supports the following settings that you can configure in the Web Dashboard UI:

online-map-item

  • Provider - Specifies whether to show Google or Bing maps.
  • Type - Specifies the map type. You can choose between RoadMap, Satellite or Hybrid.
  • Display Mode - Specifies whether to show markers or routes.

Web Page

View Script: WebPageItem.js

A custom Web Page item displays a single web page or a set of pages. You can use the Web Page as a detail item along with the Master-Filtering feature. The content is rendered inside the Inline Frame element (<iframe>). This custom item supports the following setting that you can configure in the Web Dashboard UI:

web-page-item

  • URL - Specifies a web page URL. You can set a single page as well as a set of pages (e.g., 'https://en.wikipedia.org/wiki/{0}'). If you add a dimension and specify a placeholder, the data source field returns strings that will be inserted in the position of the {0} placeholder. Thus, the Web Page item joins the specified URL with the current dimension value and displays the page located by this address.

Tasks Dashboard

The dashboard displays tasks. Select the task to display detailed information in the Grid.

This dashboard contains the following custom item:

Gannt Item

View Script: GanttItem.js

A custom Gannt item displays the task flow and dependencies between tasks. This item uses dxGantt as an underlying UI component.

gantt-item

Departments Dashboard

The dashboard displays departmental data. Use the custom Tree View item to filter detailed information in the Grid.

This dashboard contains the following custom item:

Hierarchical Tree View

View Script: HierarchicalTreeViewItem.js

A custom Tree View item can display hierarchical data. This item uses dxTreeView as an underlying UI component.

hierachical-tree-view

License

These extensions are distributed under the MIT license (free and open-source), but can only be used with a commercial DevExpress Dashboard software product. You can review the license terms or download a free trial version of the Dashboard suite at DevExpress.com.

Documentation

More Examples

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

Example Code

dashboard-react-app/src/items/SimpleTableItem.js
JavaScript
import { CustomItemViewer } from 'devexpress-dashboard/common' import { CustomItem } from 'devexpress-dashboard/model' import { FormItemTemplates } from 'devexpress-dashboard/designer' const SIMPLE_TABLE_EXTENSION_NAME = 'CustomItemSimpleTable'; const svgIcon = `<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" id="` + SIMPLE_TABLE_EXTENSION_NAME + `" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> <path class="dx-dashboard-contrast-icon" d="M21,2H3C2.5,2,2,2.5,2,3v18c0,0.5,0.5,1,1,1h18c0.5,0,1-0.5,1-1V3 C22,2.5,21.5,2,21,2z M14,4v4h-4V4H14z M10,10h4v4h-4V10z M4,4h4v4H4V4z M4,10h4v4H4V10z M4,20v-4h4v4H4z M10,20v-4h4v4H10z M20,20 h-4v-4h4V20z M20,14h-4v-4h4V14z M20,8h-4V4h4V8z"/> </svg>`; const simpleTableMetadata = { bindings: [{ propertyName: 'customDimensions', dataItemType: 'Dimension', array: true, displayName: "Custom Dimensions" }, { propertyName: 'customMeasure', dataItemType: 'Measure', displayName: "Custom Measure" }], customProperties: [{ ownerType: CustomItem, propertyName: 'showHeaders', valueType: 'string', defaultValue: 'Auto', }], optionsPanelSections: [{ title: "Custom Options", items: [{ dataField: 'showHeaders', template: FormItemTemplates.buttonGroup, editorOptions: { items: [{ text: 'Auto' }, { text: 'Off' }, { text: 'On' }], } },{ dataField: 'textColor', label: { text: 'Text Color' }, template: FormItemTemplates.buttonGroup, editorOptions: { items: [{ text: 'Light' }, { text: 'Dark' }] } }] }], icon: SIMPLE_TABLE_EXTENSION_NAME, title: 'Simple Table' }; class SimpleTableItemViewer extends CustomItemViewer { renderContent(element, changeExisting) { if (!changeExisting) { while (element.firstChild) element.removeChild(element.firstChild); element.style.overflow = 'auto'; this.tableElt = document.createElement('table'); this.tableElt.setAttribute("border", "1"); this.tableElt.setAttribute("cellspacing", "0"); element.append(this.tableElt); } this._update(this.getPropertyValue('showHeaders')); } _getTextColor() { switch (this.getPropertyValue('textColor')) { case 'Light': return "gray"; case 'Dark': return "black"; default: } } _update(mode) { while (this.tableElt.firstChild) this.tableElt.removeChild(this.tableElt.firstChild); if (mode !== 'Off') { let bindingValues = this.getBindingValue('customDimensions').concat(this.getBindingValue('customMeasure')); this._addTableRow(bindingValues.map(function (item) { return item.displayName(); }), true); } this.iterateData(rowDataObject => { let valueTexts = rowDataObject.getDisplayText('customDimensions').concat(rowDataObject.getDisplayText('customMeasure')); this._addTableRow(valueTexts, false); }); } _addTableRow(rowValues, isHeader) { const row = document.createElement('tr'); rowValues.forEach(text => { const cell = document.createElement(isHeader ? 'th' : 'td'); cell.style.padding = '3px'; cell.innerText = text; cell.style.color = this._getTextColor(); row.appendChild(cell); }); this.tableElt.appendChild(row); } } class SimpleTableItem { constructor(dashboardControl) { dashboardControl.registerIcon(svgIcon); this.name = SIMPLE_TABLE_EXTENSION_NAME; this.metaData = simpleTableMetadata; } createViewerItem(model, $element, content) { return new SimpleTableItemViewer(model, $element, content); } } export default SimpleTableItem;
dashboard-react-app/src/items/PolarChartItem.js
JavaScript
import { CustomItemViewer } from 'devexpress-dashboard/common' import { CustomItem } from 'devexpress-dashboard/model' import dxPolarChart from 'devextreme/viz/polar_chart' const POLAR_CHART_EXTENSION_NAME = 'PolarChart'; const svgIcon = `<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" id="` + POLAR_CHART_EXTENSION_NAME + `" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> <path class="dx-dashboard-contrast-icon" d="M12,1C5.9,1,1,5.9,1,12s4.9,11,11,11s11-4.9,11-11S18.1,1,12,1z M12,21c-5,0-9-4-9-9s4-9,9-9s9,4,9,9S17,21,12,21z" /> <path class="dx-dashboard-accent-icon" d="M17,10c-0.6,0-1.1,0.2-1.5,0.4L13.8,9C13.9,8.7,14,8.4,14,8c0-1.7-1.3-3-3-3S8,6.3,8,8c0,1,0.5,2,1.3,2.5L8.7,13C7.2,13.2,6,14.4,6,16c0,1.7,1.3,3,3,3s3-1.3,3-3c0,0,0,0,0-0.1l2.7-1c0.6,0.7,1.4,1.1,2.3,1.1c1.7,0,3-1.3,3-3S18.7,10,17,10z M9,17c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S9.6,17,9,17z M10,8c0-0.6,0.4-1,1-1s1,0.4,1,1s-0.4,1-1,1S10,8.6,10,8zM14,13.1l-2.7,1c-0.2-0.2-0.4-0.4-0.6-0.6l0.6-2.5c0.4,0,0.9-0.2,1.2-0.4l1.7,1.4C14.1,12.3,14,12.6,14,13.1C14,13,14,13,14,13.1zM17,14c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S17.6,14,17,14z" /> </svg > `; const polarChartItemMetaData = { bindings: [{ propertyName: 'measureValue', dataItemType: 'Measure', displayName: 'Value', array: true, emptyPlaceholder: 'Set Value', selectedPlaceholder: 'Configure Value' }, { propertyName: 'dimensionValue', dataItemType: 'Dimension', displayName: 'Argument', array: false, enableColoring: true, enableInteractivity: true, emptyPlaceholder: 'Set Argument', selectedPlaceholder: 'Configure Argument' }], interactivity: { filter: true }, customProperties: [{ ownerType: CustomItem, propertyName: 'labelVisibleProperty', valueType: 'boolean', defaultValue: true }], optionsPanelSections: [{ title: 'Labels', items: [{ dataField: 'labelVisibleProperty', label: { text: 'Display labels' } }] }], icon: POLAR_CHART_EXTENSION_NAME, title: 'Polar Chart', index: 2 }; class PolarChartItemViewer extends CustomItemViewer { constructor(model, $container, options) { super(model, $container, options); this.dxPolarWidget = null; this.dxPolarWidgetSettings = null; } _getDataSource(){ let data = []; if (this.getBindingValue('measureValue').length > 0) { this.iterateData(function (dataRow) { var dataItem = { arg: dataRow.getValue('dimensionValue') || "", color: dataRow.getColor()[0], clientDataRow: dataRow }; var measureValues = dataRow.getValue('measureValue'); for (var i = 0; i < measureValues.length; i++) { dataItem["measureValue" + i] = measureValues[i]; } data.push(dataItem); }); } return data; } _getDxPolarWidgetSettings() { let series = []; let dataSource = this._getDataSource(); let measureValueBindings = this.getBindingValue('measureValue'); for (var i = 0; i < measureValueBindings.length; i++) { series.push({ valueField: "measureValue" + i, name: measureValueBindings[i].displayName() }); } return { dataSource: dataSource, series: series, useSpiderWeb: true, resolveLabelOverlapping: "hide", pointSelectionMode: "multiple", commonSeriesSettings: { type: "line", label: { visible: this.getPropertyValue("labelVisibleProperty") } }, "export": { enabled: false }, tooltip: { enabled: false }, onPointClick: (e) => { var point = e.target; this.setMasterFilter(point.data.clientDataRow); } }; } renderContent(element, changeExisting) { if (!changeExisting) { while (element.firstChild) element.removeChild(element.firstChild); this.dxPolarWidget = new dxPolarChart(element, this._getDxPolarWidgetSettings()); } else { this.dxPolarWidget.option(this._getDxPolarWidgetSettings()); } this.updateSelection(); } setSelection(values) { super.setSelection(this, values); this.updateSelection(); } updateSelection() { let series = this.dxPolarWidget.getAllSeries(); for (var i = 0; i < series.length; i++) { var points = series[i].getAllPoints() for (var j = 0; j < points.length; j++) { if (this.isSelected(points[j].data.clientDataRow)) points[j].select(); else points[j].clearSelection(); } } } clearSelection() { super.clearSelection(this); this.dxPolarWidget.clearSelection(); } setSize(width, height) { super.setSize(this, width, height); this.dxPolarWidget.render(); } } class PolarChartItem { constructor(dashboardControl) { dashboardControl.registerIcon(svgIcon); this.name = POLAR_CHART_EXTENSION_NAME; this.metaData = polarChartItemMetaData; } createViewerItem(model, $element, content) { return new PolarChartItemViewer(model, $element, content); } } export default PolarChartItem;
dashboard-react-app/src/items/ParameterItem.js
JavaScript
import { CustomItemViewer } from 'devexpress-dashboard/common' import { CustomItem } from 'devexpress-dashboard/model' import { FormItemTemplates } from 'devexpress-dashboard/designer' import dxButton from "devextreme/ui/button"; const PARAMETER_EXTENSION_NAME = 'ParameterItem'; const svgIcon = `<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" id="` + PARAMETER_EXTENSION_NAME + `" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> <g class="st0"> <path class="dx-dashboard-contrast-icon" d="M6,12c0.4,0,0.7,0.1,1,0.2V5c0-0.6-0.4-1-1-1S5,4.4,5,5v7.2 C5.3,12.1,5.6,12,6,12z M6,18c-0.4,0-0.7-0.1-1-0.2V19c0,0.6,0.4,1,1,1s1-0.4,1-1v-1.2C6.7,17.9,6.4,18,6,18z M12,6 c0.4,0,0.7,0.1,1,0.2V5c0-0.6-0.4-1-1-1s-1,0.4-1,1v1.2C11.3,6.1,11.6,6,12,6z M12,12c-0.4,0-0.7-0.1-1-0.2V19c0,0.6,0.4,1,1,1 s1-0.4,1-1v-7.2C12.7,11.9,12.4,12,12,12z M18,17c-0.4,0-0.7-0.1-1-0.2V19c0,0.6,0.4,1,1,1s1-0.4,1-1v-2.2C18.7,16.9,18.4,17,18,17 z M18,11c0.4,0,0.7,0.1,1,0.2V5c0-0.6-0.4-1-1-1s-1,0.4-1,1v6.2C17.3,11.1,17.6,11,18,11z"/> </g> <path class="dx-dashboard-accent-icon" d="M6,12c-1.7,0-3,1.3-3,3s1.3,3,3,3s3-1.3,3-3S7.7,12,6,12z M6,16c-0.6,0-1-0.4-1-1 s0.4-1,1-1s1,0.4,1,1S6.6,16,6,16z M12,6c-1.7,0-3,1.3-3,3s1.3,3,3,3s3-1.3,3-3S13.7,6,12,6z M12,10c-0.6,0-1-0.4-1-1s0.4-1,1-1 s1,0.4,1,1S12.6,10,12,10z M18,11c-1.7,0-3,1.3-3,3s1.3,3,3,3s3-1.3,3-3S19.7,11,18,11z M18,15c-0.6,0-1-0.4-1-1s0.4-1,1-1 s1,0.4,1,1S18.6,15,18,15z"/> </svg>`; const onOffButtons = [{ text: 'On' }, { text: 'Off' }]; const buttonsStyle = { containerHeight: 60, height: 40, width: 82, marginRight: 15, marginTop: 10 }; const parameterMetadata = { customProperties: [{ ownerType: CustomItem, propertyName: 'showHeaders', valueType: 'string', defaultValue: 'On', }, { ownerType: CustomItem, propertyName: 'showParameterName', valueType: 'string', defaultValue: 'On', }, { ownerType: CustomItem, propertyName: 'automaticUpdates', valueType: 'string', defaultValue: 'Off', }], optionsPanelSections: [{ title: 'Parameters settings', items: [{ dataField: 'showHeaders', template: FormItemTemplates.buttonGroup, editorOptions: { items: onOffButtons, }, }, { dataField: 'showParameterName', template: FormItemTemplates.buttonGroup, editorOptions: { items: onOffButtons, }, }, { dataField: 'automaticUpdates', template: FormItemTemplates.buttonGroup, editorOptions: { items: onOffButtons, }, }], }], icon: PARAMETER_EXTENSION_NAME, title: 'Parameters', }; class ParameterItemViewer extends CustomItemViewer { constructor(model, container, options, parameterExtension) { super(model, container, options); this.buttons = []; this.parameterExtension = parameterExtension; this._subscribeProperties(); this.parameterExtension.showDialogButton(false); this.parameterExtension.subscribeToContentChanges(() => { this._generateParametersContent(); }); this.dialogButtonSubscribe = this.parameterExtension.showDialogButton.subscribe(() => { this.parameterExtension.showDialogButton(false); }); } setSize(width, height) { super.setSize(width, height); this._setGridHeight(); } dispose() { super.dispose(); this.parametersContent && this.parametersContent.dispose && this.parametersContent.dispose(); this.dialogButtonSubscribe.dispose(); this.parameterExtension.showDialogButton(true); this.buttons.forEach(button => button.dispose()); } renderContent(dxElement, changeExisting) { this._element = dxElement; let element = (dxElement).jquery ? (dxElement).get(0): dxElement; if (!changeExisting) { while (element.firstChild) element.removeChild(element.firstChild); this.buttons.forEach(button => button.dispose()); element.style.overflow = 'auto'; this.gridContainer = document.createElement('div'); element.appendChild(this.gridContainer); this._generateParametersContent(); this.buttonContainer = document.createElement('div'); this.buttonContainer.style.height = buttonsStyle.containerHeight + 'px'; this.buttonContainer.style.width = buttonsStyle.width * 2 + buttonsStyle.marginRight * 2 + 'px'; this.buttonContainer.style.cssFloat = 'right'; element.appendChild(this.buttonContainer); this.buttons.push(this._createButton(this.buttonContainer, "Reset", () => { this.parametersContent.resetParameterValues(); })); this.buttons.push(this._createButton(this.buttonContainer, "Submit", () => { this._submitValues(); })); if (this.getPropertyValue('automaticUpdates') !== 'Off') this.buttonContainer.style.display = 'none'; } } _generateParametersContent() { this.parametersContent = this.parameterExtension.renderContent(this.gridContainer); this.parametersContent.valueChanged.add(() => this._updateParameterValues()); this._setGridHeight(); this._update({ showHeaders: this.getPropertyValue('showHeaders'), showParameterName: this.getPropertyValue('showParameterName') }); } _submitValues() { this.parametersContent.submitParameterValues(); this._update({ showHeaders: this.getPropertyValue('showHeaders'), showParameterName: this.getPropertyValue('showParameterName') }); } _updateParameterValues() { if (this.getPropertyValue('automaticUpdates') !== 'Off') { this._submitValues() } } _setGridHeight() { let gridHeight = this.contentHeight(); if (this.getPropertyValue('automaticUpdates') === 'Off') gridHeight -= buttonsStyle.containerHeight; this.parametersContent.grid.option('height', gridHeight); } _createButton(container, buttonText, onClick) { let button = document.createElement("div"); button.style.marginRight = buttonsStyle.marginRight + 'px'; button.style.marginTop = buttonsStyle.marginTop + 'px'; container.appendChild(button); return new dxButton(button, { text: buttonText, height: buttonsStyle.height + 'px', width: buttonsStyle.width + 'px', onClick: onClick }); } _subscribeProperties() { this.subscribe('showHeaders', (showHeaders) => { this._update({ showHeaders: showHeaders }); }); this.subscribe('showParameterName', (showParameterName) => { this._update({ showParameterName: showParameterName, rerender: true }); }); this.subscribe('automaticUpdates', (automaticUpdates) => { this._update({ automaticUpdates: automaticUpdates }) }); } _update(options) { if (!!options.showHeaders) { this.parametersContent.grid.option('showColumnHeaders', options.showHeaders === 'On'); } if(!!options.showParameterName) { this.parametersContent.valueChanged.empty(); this.parametersContent.grid.columnOption(0, 'visible', options.showParameterName === 'On'); this.parametersContent.valueChanged.add(() => { return this._updateParameterValues(); }); } if(!!options.automaticUpdates) { switch (options.automaticUpdates) { case 'Off': this.buttonContainer.style.display = 'block'; break; case 'On': this.buttonContainer.style.display = 'none'; break; default: break; } } this._setGridHeight(); if(options.rerender) this.renderContent(this._element, false); } } class ParameterItem { constructor(dashboardControl) { dashboardControl.registerIcon(svgIcon); this.dashboardControl = dashboardControl; this.name = PARAMETER_EXTENSION_NAME; this.metaData = parameterMetadata; } createViewerItem = (model, $element, content) => { var parameterExtension = this.dashboardControl.findExtension("dashboard-parameter-dialog"); if (!parameterExtension){ throw Error('The "dashboard-parameter-dialog" extension does not exist. To register this extension, use the DashboardControl.registerExtension method.'); } return new ParameterItemViewer(model, $element, content, parameterExtension); } } export default ParameterItem;
dashboard-react-app/src/items/OnlineMapItem.js
JavaScript
import { CustomItemViewer } from 'devexpress-dashboard/common' import { CustomItem } from 'devexpress-dashboard/model' import { FormItemTemplates } from 'devexpress-dashboard/designer' import dxMap from 'devextreme/ui/map' const ONLINE_MAP_EXTENSION_NAME = 'OnlineMap'; const svgIcon = `<svg version="1.1" id="` + ONLINE_MAP_EXTENSION_NAME + `" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> <path class="dx_darkgray" d="M12,1C8.1,1,5,4.1,5,8c0,3.9,3,10,7,15c4-5,7-11.1,7-15C19,4.1,15.9,1,12,1z M12,12c-2.2,0-4-1.8-4-4 c0-2.2,1.8-4,4-4s4,1.8,4,4C16,10.2,14.2,12,12,12z"/> <circle class="dx_red" cx="12" cy="8" r="2"/> </svg>`; const onlineMapMetadata = { bindings:[{ propertyName: 'Latitude', dataItemType: 'Dimension', array: false, enableInteractivity: true, displayName: 'Latitude', emptyPlaceholder: 'Set Latitude', selectedPlaceholder: 'Configure Latitude', constraints: { allowedTypes: ['Integer', 'Float', 'Double', 'Decimal'] } }, { propertyName: 'Longitude', dataItemType: 'Dimension', array: false, enableInteractivity: true, displayName: 'Longitude', emptyPlaceholder: 'Set Longitude', selectedPlaceholder: 'Configure Longitude', constraints: { allowedTypes: ['Integer', 'Float', 'Double', 'Decimal'] } }], customProperties: [{ ownerType: CustomItem, propertyName: 'Provider', valueType: 'string', defaultValue: 'Bing', },{ ownerType: CustomItem, propertyName: 'Type', valueType: 'string', defaultValue: 'RoadMap', },{ ownerType: CustomItem, propertyName: 'DisplayMode', valueType: 'string', defaultValue: 'Markers', }], optionsPanelSections: [{ title: 'Custom Options', items: [{ dataField: 'Provider', template: FormItemTemplates.buttonGroup, editorOptions: { items: [{ text: 'Google' }, { text: 'Bing' }] }, },{ dataField: 'Type', template: FormItemTemplates.buttonGroup, editorOptions: { items: [{ text: 'RoadMap' }, { text: 'Satellite' }, { text: 'Hybrid' }] }, },{ dataField: 'DisplayMode', template: FormItemTemplates.buttonGroup, editorOptions: { keyExpr: 'value', items: [{ value: 'Markers', text: 'Markers' }, { value: 'Routes', text: 'Routes' }, { value: 'MarkersAndRoutes', text: 'All' }], }, }] }], interactivity: { filter: true, drillDown: false }, icon: ONLINE_MAP_EXTENSION_NAME, title: 'Online Map', index: 1 }; class OnlineMapItemViewer extends CustomItemViewer { constructor(model, $container, options) { super(model, $container, options); this.mapViewer = null; } _onClick(row) { this.setMasterFilter(row); this._updateSelection(); } _updateSelection() { let markers = this.mapViewer.option('markers'); markers.forEach(marker => { marker.iconSrc = this.isSelected(marker.tag) ? "https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/maps/map-marker.png" : null; }); this.mapViewer.option('autoAdjust', false); this.mapViewer.option('markers', markers); } setSize(width, height) { super.setSize(width, height); let contentWidth = this.contentWidth(), contentHeight = this.contentHeight(); this.mapViewer.option('width', contentWidth); this.mapViewer.option('height', contentHeight); } setSelection(values) { super.setSelection(values); this._updateSelection(); } clearSelection() { super.clearSelection(); this._updateSelection(); } renderContent(element, changeExisting) { let markers = [], routes = [], mode = this.getPropertyValue('DisplayMode'), showMarkers = mode === 'Markers' || mode === 'MarkersAndRoutes' || this.canMasterFilter(), showRoutes = mode === 'Routes' || mode === 'MarkersAndRoutes'; if (this.getBindingValue('Latitude').length > 0 && this.getBindingValue('Longitude').length > 0) { this.iterateData(row => { let latitude = row.getValue('Latitude')[0]; let longitude = row.getValue('Longitude')[0]; if (latitude && longitude) { if (showMarkers) { markers.push({ location: { lat: latitude, lng: longitude }, iconSrc: this.isSelected(row) ? "https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/maps/map-marker.png" : null, onClick: args => this._onClick(row), tag: row }); } if (showRoutes) { routes.push([latitude, longitude]); } } }); } let autoAdjust = markers.length > 0 || routes.length > 0, options = { provider: this.getPropertyValue('Provider').toLowerCase(), type: this.getPropertyValue('Type').toLowerCase(), controls: true, zoom: autoAdjust ? 1000 : 1, autoAdjust: autoAdjust, width: this.contentWidth(), height: this.contentHeight(), // Use the template below to authenticate the application within the required map provider. //apiKey: { // bing: 'BINGAPIKEY', // google: 'GOOGLEAPIKEY' //}, markers: markers, routes: routes.length > 0 ? [{ weight: 6, color: 'blue', opacity: 0.5, mode: '', locations: routes }] : [] }; if (changeExisting && this.mapViewer) { this.mapViewer.option(options); } else { this.mapViewer = new dxMap(element, options); } } } class OnlineMapItem { constructor(dashboardControl) { dashboardControl.registerIcon(svgIcon); this.name = ONLINE_MAP_EXTENSION_NAME; this.metaData = onlineMapMetadata; } createViewerItem(model, $element, content) { return new OnlineMapItemViewer(model, $element, content); } } export default OnlineMapItem;
dashboard-react-app/src/items/WebPageItem.js
JavaScript
import { CustomItemViewer } from 'devexpress-dashboard/common' import { CustomItem } from 'devexpress-dashboard/model' const WEBPAGE_EXTENSION_NAME = 'WebPage'; const svgIcon = `<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" id="` + WEBPAGE_EXTENSION_NAME + `" xmlns = "http://www.w3.org/2000/svg" xmlns: xlink = "http://www.w3.org/1999/xlink" x = "0px" y = "0px" viewBox = "0 0 24 24" style = "enable-background:new 0 0 24 24;" xml: space = "preserve" > <path class="dx-dashboard-contrast-icon" d="M20.7,4.7l-3.4-3.4C17.1,1.1,16.9,1,16.6,1H4C3.4,1,3,1.4,3,2v20c0,0.6,0.4,1,1,1h16 c0.6,0,1-0.4,1-1V5.4C21,5.1,20.9,4.9,20.7,4.7z M19,21H5V3h11v2c0,0.6,0.4,1,1,1h2V21z"/> <path class="dx-dashboard-accent-icon" d="M13.7,17.5c-0.2-0.4-1.6-1.8-1.4-2.2s0.2-1.1-0.1-1.3c-0.3-0.1-0.7,0.1-0.7-0.2 c-0.1-0.3-1.1-0.2-1.2-1.6c-0.1-1.5-0.6-2-1.2-2s-1.6,0.6-1.5,0c0-0.1,0-0.2,0-0.3c-1,1-1.6,2.5-1.6,4.1c0,3.3,2.7,6,6,6 c0.6,0,1.1-0.1,1.6-0.2C13.7,19.1,13.9,17.8,13.7,17.5z M12,8c-1.1,0-2.2,0.3-3.1,0.9H9c1,0.2,3.1,0.7,3.1,0.3S12,8.3,12.2,8.4 c0.2,0.2,0.8,0.7,0.6,1S12,10,12.2,10.3c0.2,0.2,0.8,0.6,1,0.4s-0.1-0.9,0.2-0.8c0.3,0,1.8,0.8,1.3,1.1s-1.4,1.9-1.9,2 s-0.9,0.2-0.8,0.6c0.2,0.5,0.5,0.2,0.7,0.3c0.1,0.1,0.1,0.4,0.3,0.6s0.4,0.1,0.7,0.1c0.3-0.1,2.5,0.9,2.3,1.4 c-0.2,0.5-0.2,1.2-1,2.1c-0.5,0.5-0.7,1.1-0.9,1.5c2.3-0.8,4-3,4-5.6C18,10.7,15.3,8,12,8z"/> </svg>`; const webPageMetadata = { bindings: [{ propertyName: 'Attribute', dataItemType: 'Dimension', array: false, displayName: "Attribute", emptyPlaceholder: 'Set Attribute', selectedPlaceholder: "Configure Attribute" }], customProperties: [{ ownerType: CustomItem, propertyName: 'Url', valueType: 'string', defaultValue: 'https://en.m.wikipedia.org/wiki/{0}', }], optionsPanelSections: [{ title: 'Custom Options', items: [{ dataField: 'Url', editorType: 'dxTextBox', }] }], icon: WEBPAGE_EXTENSION_NAME, title: 'Web Page', index: 2 }; class WebPageItemViewer extends CustomItemViewer { constructor(model, $container, options) { super(model, $container, options); this.mapViewer = null; } renderContent(element, changeExisting) { var attribute; if(!changeExisting || !this.iframe) { while (element.firstChild) element.removeChild(element.firstChild); this.iframe = document.createElement('iframe'); this.iframe.width = '100%'; this.iframe.height = '100%'; this.iframe.style.border = "none"; element.appendChild(this.iframe); } this.iterateData(row => { if(!attribute) { attribute = row.getDisplayText('Attribute')[0]; } }); this.iframe.src = this.getPropertyValue('Url').replace('{0}', attribute); } } class WebPageItem { constructor(dashboardControl) { dashboardControl.registerIcon(svgIcon); this.name = WEBPAGE_EXTENSION_NAME; this.metaData = webPageMetadata; } createViewerItem(model, $element, content) { return new WebPageItemViewer(model, $element, content); } } export default WebPageItem;
dashboard-react-app/src/items/GanttItem.js
JavaScript
import { CustomItemViewer } from 'devexpress-dashboard/common' import dxGantt from 'devextreme/ui/gantt' import notify from "devextreme/ui/notify"; const GANTT_EXTENSION_NAME = 'GanttItem'; const svgIcon = `<? xml version = "1.0" encoding = "utf-8"?> <svg version="1.1" id="` + GANTT_EXTENSION_NAME + `" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> <path class="dx-dashboard-contrast-icon" d="M23,2c0-0.6-0.4-1-1-1H2C1.4,1,1,1.4,1,2v20c0,0.6,0.4,1,1,1h20c0.6,0,1-0.4,1-1 V2z M21,21H3V3h18V21z"/> <path class="dx-dashboard-accent-icon" d="M12,9H5V5h7V9z M19,10H9v4h10V10z M15,15H7v4h8V15z"/> </svg>`; const ganttItemMetadata = { bindings: [{ propertyName: 'ID', dataItemType: 'Dimension', displayName: 'ID', enableInteractivity: true }, { propertyName: 'ParentID', dataItemType: 'Dimension', displayName: 'Parent ID', enableInteractivity: true }, { propertyName: 'Text', dataItemType: 'Dimension', displayName: 'Text', enableInteractivity: true }, { propertyName: 'StartDate', dataItemType: 'Dimension', displayName: 'Start Date', enableInteractivity: true }, { propertyName: 'FinishDate', dataItemType: 'Dimension', displayName: 'Finish Date', enableInteractivity: true }], interactivity: { filter: true }, icon: GANTT_EXTENSION_NAME, title: 'Gantt Chart', index: 0 }; class GanttItemViewer extends CustomItemViewer { constructor(model, $container, options) { super(model, $container, options); this.dxGanttWidget = null; } _getDataSource() { let data = []; let datesValid = true; this.iterateData(function (dataRow) { data.push({ id: dataRow.getValue('ID')[0], parentId: dataRow.getValue('ParentID')[0], title: dataRow.getValue('Text')[0], start: dataRow.getValue('StartDate')[0], end: dataRow.getValue('FinishDate')[0], clientDataRow: dataRow }); let currentItem = data[data.length - 1]; if ((currentItem.start && !(currentItem.start instanceof Date)) || (currentItem.end && !(currentItem.end instanceof Date))) datesValid = false; }); if (!datesValid) { notify("Gantt: 'Start Date' or 'Finish Date' is not a Date object.", "warning", 3000); return []; } return data; } _getDxGanttWidgetSettings() { return { rootValue: -1, tasks: { dataSource: this._getDataSource() }, columns: [{ dataField: "title", caption: "Subject", width: 300, }, { dataField: "start", caption: "Start Date" }, { dataField: "end", caption: "End Date" }], onTaskClick: e => { var tasks = e.component.option("tasks.dataSource"); var clickedTask = tasks.filter(item => item.id === e.key)[0]; this.setMasterFilter(clickedTask.clientDataRow); }, scaleType: "days", taskListWidth: 500, }; } setSelection(values) { super.setSelection.call(this, values); let tasks = this.dxGanttWidget.option("tasks.dataSource"); tasks.forEach(item => { if (this.isSelected(item.clientDataRow)) this.dxGanttWidget.option("selectedRowKey", item.id); }); } clearSelection() { super.clearSelection.call(this); this.dxGanttWidget.option("selectedRowKey", null); } setSize(width, height) { super.setSize.call(this, width, height); this.dxGanttWidget.repaint(); } renderContent(element, changeExisting) { if (!changeExisting) { while (element.firstChild) element.removeChild(element.firstChild); this.dxGanttWidget = new dxGantt(element, this._getDxGanttWidgetSettings()); } else { this.dxGanttWidget.option(this._getDxGanttWidgetSettings()); } } } class GanttItem { constructor(dashboardControl) { dashboardControl.registerIcon(svgIcon); this.name = GANTT_EXTENSION_NAME; this.metaData = ganttItemMetadata; } createViewerItem(model, $element, content) { return new GanttItemViewer(model, $element, content); } } export default GanttItem;
dashboard-react-app/src/items/HierarchicalTreeViewItem.js
JavaScript
import { CustomItemViewer } from 'devexpress-dashboard/common' import dxTreeView from 'devextreme/ui/tree_view' const HIERARCIAL_TREE_VIEW_EXTENSION_NAME = 'TreeView'; const svgIcon = `<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" id="` + HIERARCIAL_TREE_VIEW_EXTENSION_NAME + `" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> <polygon class="dx-dashboard-contrast-icon" points="12,13 12,11 8,11 8,8 6,8 6,21 12,21 12,19 8,19 8,13 "/> <path class="dx-dashboard-accent-icon" d="M10,7H4C3.5,7,3,6.6,3,6V2c0-0.5,0.5-1,1-1h6c0.6,0,1,0.5,1,1v4C11,6.6,10.6,7,10,7z M21,14v-4c0-0.6-0.5-1-1-1h-6c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h6C20.5,15,21,14.6,21,14z M21,22v-4c0-0.5-0.5-1-1-1h-6 c-0.6,0-1,0.5-1,1v4c0,0.5,0.4,1,1,1h6C20.5,23,21,22.5,21,22z"/> </svg>`; const treeViewMetadata = { bindings: [{ propertyName: 'idBinding', dataItemType: 'Dimension', array: false, displayName: 'ID', placeholder: 'Add ID', configurePlaceholder: 'Configure ID', }, { propertyName: 'parentIdBinding', dataItemType: 'Dimension', array: false, displayName: 'Parent ID', placeholder: 'Add Parent ID', configurePlaceholder: 'Configure Parent ID', }, { propertyName: 'dimensionsBinding', dataItemType: 'Dimension', array: false, displayName: 'Dimensions', placeholder: 'Add Dimension', configurePlaceholder: 'Configure Dimension', enableInteractivity: true }], interactivity: { filter: true }, icon: HIERARCIAL_TREE_VIEW_EXTENSION_NAME, // Uncomment the line below to place this custom item in the "Filter" group: //groupName: 'filter', title: 'Hierarchical Tree View', index: 110 }; class TreeViewViewer extends CustomItemViewer { constructor(model, $container, options, dashboardControl) { super(model, $container, options); this.dxTreeViewWidget = null; this._requiredBindingsCount = 3; this.dashboardControl = dashboardControl; } renderContent(element, changeExisting) { let dataSource = []; //Check Bindings let bindings = this.getBindingValue('dimensionsBinding').concat(this.getBindingValue('idBinding')).concat(this.getBindingValue('parentIdBinding')); if (bindings.length !== this._requiredBindingsCount) return; //Get Data Source this.iterateData(function (dataRow) { let row = { ID: dataRow.getDisplayText('idBinding')[0], ParentID: dataRow.getDisplayText('parentIdBinding')[0] !== '-1' ? dataRow.getDisplayText('parentIdBinding')[0] : null, DisplayField: dataRow.getDisplayText('dimensionsBinding')[0], }; row._customData = dataRow; dataSource.push(row); }); let treeViewOptions = { items: dataSource, dataStructure: "plain", parentIdExpr: "ParentID", keyExpr: "ID", displayExpr: "DisplayField", selectionMode: "multiple", selectNodesRecursive: false, onItemClick: e => { if(this.getMasterFilterMode() === 'Multiple' && this.allowMultiselection) { this.setMasterFilterRecursive(e.node); } else { this.setMasterFilter(e.itemData._customData); } }, onContentReady: e => { this.updateTreeViewSelection(); } }; if (!changeExisting) { while (element.firstChild) element.removeChild(element.firstChild); let div = document.createElement('div'); element.appendChild(div); this.dxTreeViewWidget = new dxTreeView(div, treeViewOptions); } else { this.dxTreeViewWidget.option(treeViewOptions); } } setSelection(values) { super.setSelection(values); this.updateTreeViewSelection(); } clearSelection() { super.clearSelection(); this.updateTreeViewSelection(); } updateTreeViewSelection() { if (this.dxTreeViewWidget) { this.dxTreeViewWidget.unselectAll(); let nodes = this.dxTreeViewWidget.option('items'); nodes.forEach(item => { if(this.isSelected(item._customData)) this.dxTreeViewWidget.selectItem(item.ID); }); } } setMasterFilterRecursive(node) { this.setMasterFilter(node.itemData._customData); node.children.forEach(x => this.setMasterFilterRecursive(x)); } } class TreeViewItem { constructor(dashboardControl) { dashboardControl.registerIcon(svgIcon); this.name = HIERARCIAL_TREE_VIEW_EXTENSION_NAME; this.metaData = treeViewMetadata; this.dashboardControl = dashboardControl; } createViewerItem = (model, $element, content) => { return new TreeViewViewer(model, $element, content, this.dashboardControl); } } export default TreeViewItem;
dashboard-react-app/src/items/FunnelD3Item.js
JavaScript
import { CustomItemViewer } from 'devexpress-dashboard/common' import { CustomItem } from 'devexpress-dashboard/model' import { FormItemTemplates } from 'devexpress-dashboard/designer' import * as D3Funnel from 'd3-funnel' import * as $ from 'jquery' const FUNNEL_D3_EXTENSION_NAME = 'FunnelD3'; const svgIcon = ` <svg version="1.1" id="` + FUNNEL_D3_EXTENSION_NAME + `" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> <polygon class="dx_green" points="2,1 22,1 16,8 8,8 "/> <polygon class="dx_blue" points="8,9 16,9 14,15 10,15 "/> <polygon class="dx_red" points="10,16 14,16 13,23 11,23 "/> </svg>`; const funnelMeta = { bindings: [{ propertyName: 'Values', dataItemType: 'Measure', array: true, enableColoring: true, displayName: 'Values', emptyPlaceholder: 'Set Value', selectedPlaceholder: 'Configure Value' }, { propertyName: 'Arguments', dataItemType: 'Dimension', array: true, enableInteractivity: true, enableColoring: true, displayName: 'Arguments', emptyPlaceholder: 'Set Argument', selectedPlaceholder: 'Configure Argument' }], customProperties: [{ ownerType: CustomItem, propertyName: 'FillType', valueType: 'string', defaultValue: 'Solid', }, { ownerType: CustomItem, propertyName: 'IsCurved', valueType: 'boolean', defaultValue: false, }, { ownerType: CustomItem, propertyName: 'IsDynamicHeight', valueType: 'boolean', defaultValue: true, }, { ownerType: CustomItem, propertyName: 'PinchCount', valueType: 'number', defaultValue: 0, }], optionsPanelSections: [{ title: 'Settings', items: [{ dataField: 'FillType', template: FormItemTemplates.buttonGroup, editorOptions: { items: [{ text: 'Solid' }, { text: 'Gradient' }] }, }, { dataField: 'IsCurved', label: { text: 'Curved' }, template: FormItemTemplates.buttonGroup, editorOptions: { keyExpr: 'value', items: [{ value: false, text: 'No', }, { value: true, text: 'Yes', }] }, }, { dataField: 'IsDynamicHeight', label: { text: 'Dynamic Height' }, template: FormItemTemplates.buttonGroup, editorOptions: { keyExpr: 'value', items: [{ value: false, text: 'No', }, { value: true, text: 'Yes', }] }, }, { dataField: 'PinchCount', editorType: 'dxNumberBox', editorOptions: { min: 0, }, }], }], interactivity: { filter: true, drillDown: true }, icon: FUNNEL_D3_EXTENSION_NAME, title: 'Funnel D3', }; class FunnelD3ItemViewer extends CustomItemViewer { funnelSettings; funnelViewer; selectionValues; exportingImage; funnelContainer; constructor(model, $container, options) { super(model, $container, options); this.funnelSettings = undefined; this.funnelViewer = null; this.selectionValues = []; this.exportingImage = new Image(); this._subscribeProperties(); } renderContent(element, changeExisting) { let htmlElement= element instanceof $ ? element.get(0): element; let data = this._getDataSource(); if (!this._ensureFunnelLibrary(htmlElement)) return; if (!!data) { if (!changeExisting || !this.funnelViewer) { while (htmlElement.firstChild) htmlElement.removeChild(htmlElement.firstChild); this.funnelContainer = document.createElement('div'); this.funnelContainer.style.margin = '20px'; this.funnelContainer.style.height = 'calc(100% - 40px)'; htmlElement.appendChild(this.funnelContainer); this.funnelViewer = new D3Funnel(this.funnelContainer); } this._update(data, this._getFunnelSizeOptions()); } else { while (htmlElement.firstChild) htmlElement.removeChild(htmlElement.firstChild); this.funnelViewer = null; } } setSize(width, height) { super.setSize(width, height); this._update(null, this._getFunnelSizeOptions()); } setSelection(values) { super.setSelection(values); this._update(this._getDataSource()); } clearSelection() { super.clearSelection(); this._update(this._getDataSource()); } allowExportSingleItem() { return true; } getExportInfo() { return { image: this._getImageBase64() }; } _getFunnelSizeOptions() { if (!this.funnelContainer) return {}; return { chart: { width: this.funnelContainer.clientWidth, height: this.funnelContainer.clientHeight } }; } _getDataSource() { var bindingValues = this.getBindingValue('Values'); if (bindingValues.length === 0) return undefined; var data = []; this.iterateData((dataRow) => { var values = dataRow.getValue('Values'); var valueStr = dataRow.getDisplayText('Values'); var color = dataRow.getColor('Values'); if (this._hasArguments()) { var labelText = dataRow.getDisplayText('Arguments').join(' - ') + ': ' + valueStr; data.push([{ data: dataRow, text: labelText, color: color[0] }].concat(values));//0 - 'layer' index for color value } else { data = values.map((value, index) => { return [{ text: bindingValues[index].displayName() + ': ' + valueStr[index], color: color[index] }, value]; }); } }); return data.length > 0 ? data : undefined; } _ensureFunnelLibrary(htmlElement) { if (!D3Funnel) { htmlElement.innerHTML = ''; var textDiv = document.createElement('div'); textDiv.style.position = 'absolute'; textDiv.style.top = '50%'; textDiv.style.transform = 'translateY(-50%)'; textDiv.style.width = '95%'; textDiv.style.color = '#CF0F2E'; textDiv.style.textAlign = 'center'; textDiv.innerText = "'D3Funnel' cannot be displayed. You should include 'd3.v3.min.js' and 'd3-funnel.js' libraries."; htmlElement.appendChild(textDiv); return false; } return true; } _ensureFunnelSettings() { var getSelectionColor = (hexColor) => { return this.funnelViewer.colorizer.shade(hexColor, -0.5); }; if (!this.funnelSettings) { this.funnelSettings = { data: undefined, options: { chart: { bottomPinch: this.getPropertyValue('PinchCount'), curve: { enabled: this.getPropertyValue('IsCurved') } }, block: { dynamicHeight: this.getPropertyValue('IsDynamicHeight'), fill: { scale: (index) => { var obj = this.funnelSettings.data[index][0]; return obj.data && this.isSelected(obj.data) ? getSelectionColor(obj.color) : obj.color; }, type: this.getPropertyValue('FillType').toLowerCase() } }, label: { format: (label, value) => { return label.text; } }, events: { click: { block: (e) => this._onClick(e) } } } }; } this.funnelSettings.options.block.highlight = this.canDrillDown() || this.canMasterFilter(); return this.funnelSettings; } _onClick(e) { if (!this._hasArguments() || !e.label) return; var row = e.label.raw.data; if (this.canDrillDown(row)) this.drillDown(row); else if (this.canMasterFilter(row)) { this.setMasterFilter(row); this._update(); } } _subscribeProperties() { this.subscribe('IsCurved', (isCurved) => this._update(null, { chart: { curve: { enabled: isCurved } } })); this.subscribe('IsDynamicHeight', (isDynamicHeight) => this._update(null, { block: { dynamicHeight: isDynamicHeight } })); this.subscribe('PinchCount', (count) => this._update(null, { chart: { bottomPinch: count } })); this.subscribe('FillType', (type) => this._update(null, { block: { fill: { type: type.toLowerCase() } } })); } _update(data, options) { this._ensureFunnelSettings(); if (!!data) { this.funnelSettings.data = data; } if (!!options) { $.extend(true, this.funnelSettings.options, options); } if (!!this.funnelViewer) { this.funnelViewer.draw(this.funnelSettings.data, this.funnelSettings.options); this._updateExportingImage(); } } _updateExportingImage() { const svg = this.funnelContainer.firstElementChild; const str = new XMLSerializer().serializeToString(svg), encodedData = 'data:image/svg+xml;base64,' + window.btoa(window['unescape'](encodeURIComponent(str))); this.exportingImage.src = encodedData; } _hasArguments() { return this.getBindingValue('Arguments').length > 0; } _getImageBase64() { let canvas = document.createElement('canvas'); canvas.width = this.funnelContainer.clientWidth; canvas.height = this.funnelContainer.clientHeight; const canvasContext = canvas.getContext('2d'); canvasContext && canvasContext.drawImage(this.exportingImage, 0, 0); return canvas.toDataURL().replace('data:image/png;base64,', ''); } } class FunnelD3Item { constructor(dashboardControl) { dashboardControl.registerIcon(svgIcon); this.name = FUNNEL_D3_EXTENSION_NAME; this.metaData = funnelMeta; } createViewerItem(model, $element, content) { return new FunnelD3ItemViewer(model, $element, content); } } export default FunnelD3Item;
dashboard-react-app/src/App.tsx
Code
import React from 'react'; import './App.css'; import DashboardControl from 'devexpress-dashboard-react'; import {DashboardPanelExtension} from 'devexpress-dashboard/common'; import OnlineMapItem from './items/OnlineMapItem'; import WebPageItem from './items/WebPageItem'; import SimpleTableItem from './items/SimpleTableItem'; import PolarChartItem from './items/PolarChartItem'; import GanttItem from './items/GanttItem'; import ParameterItem from './items/ParameterItem'; import TreeViewItem from './items/HierarchicalTreeViewItem'; import FunnelD3Item from './items/FunnelD3Item'; function onBeforeRender(e) { var dashboardControl = e.component; dashboardControl.registerExtension(new DashboardPanelExtension(dashboardControl)); dashboardControl.registerExtension(new SimpleTableItem(dashboardControl)); dashboardControl.registerExtension(new PolarChartItem(dashboardControl)); dashboardControl.registerExtension(new ParameterItem(dashboardControl)); dashboardControl.registerExtension(new OnlineMapItem(dashboardControl)); dashboardControl.registerExtension(new WebPageItem(dashboardControl)); dashboardControl.registerExtension(new GanttItem(dashboardControl)); dashboardControl.registerExtension(new TreeViewItem(dashboardControl)); dashboardControl.registerExtension(new FunnelD3Item(dashboardControl)); } function App() { return ( <div style={{ position : 'absolute', top : '0px', left: '0px', right : '0px', bottom: '0px' }}> <DashboardControl style={{ height: '100%' }} endpoint="http://localhost:5000/api/dashboard" onBeforeRender = { onBeforeRender }> </DashboardControl> </div> ); } export default App;

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.