DevExtreme v24.2 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

React Charts - Drill-Down Chart

This demo shows a drill-down chart that visualizes data on two hierarchical views. The main view displays the population breakdown by continent. When you click the bar, a detailed view reveals the most populated countries on the selected continent.

Backend API
import React, { useCallback, useState } from 'react'; import { Chart, Series, Legend, ValueAxis, ChartTypes, } from 'devextreme-react/chart'; import { Button } from 'devextreme-react/button'; import service from './data.ts'; const colors = ['#6babac', '#e55253']; function App() { const [isFirstLevel, setIsFirstLevel] = useState(true); const [data, setData] = useState(service.filterData('')); const customizePoint = useCallback((): { color: string, hoverStyle?: { color?: string, hatching?: string } } => ({ color: colors[Number(isFirstLevel)], hoverStyle: !isFirstLevel ? { hatching: 'none', } : {}, }), [isFirstLevel]); const onPointClick = useCallback((e: ChartTypes.PointClickEvent) => { if (isFirstLevel) { setIsFirstLevel(false); setData(service.filterData(e.target.originalArgument.toString())); } }, [isFirstLevel, setData, setIsFirstLevel]); const onButtonClick = useCallback(() => { if (!isFirstLevel) { setIsFirstLevel(true); setData(service.filterData('')); } }, [isFirstLevel, setData, setIsFirstLevel]); return ( <div> <Chart id="chart" title="The Most Populated Countries by Continents" customizePoint={customizePoint} onPointClick={onPointClick} className={isFirstLevel ? 'pointer-on-bars' : ''} dataSource={data} > <Series type="bar" /> <ValueAxis showZero={false} /> <Legend visible={false} /> </Chart> <Button className="button-container" text="Back" icon="chevronleft" visible={!isFirstLevel} onClick={onButtonClick} /> </div> ); } export default App;
import React, { useCallback, useState } from 'react'; import { Chart, Series, Legend, ValueAxis, } from 'devextreme-react/chart'; import { Button } from 'devextreme-react/button'; import service from './data.js'; const colors = ['#6babac', '#e55253']; function App() { const [isFirstLevel, setIsFirstLevel] = useState(true); const [data, setData] = useState(service.filterData('')); const customizePoint = useCallback( () => ({ color: colors[Number(isFirstLevel)], hoverStyle: !isFirstLevel ? { hatching: 'none', } : {}, }), [isFirstLevel], ); const onPointClick = useCallback( (e) => { if (isFirstLevel) { setIsFirstLevel(false); setData(service.filterData(e.target.originalArgument.toString())); } }, [isFirstLevel, setData, setIsFirstLevel], ); const onButtonClick = useCallback(() => { if (!isFirstLevel) { setIsFirstLevel(true); setData(service.filterData('')); } }, [isFirstLevel, setData, setIsFirstLevel]); return ( <div> <Chart id="chart" title="The Most Populated Countries by Continents" customizePoint={customizePoint} onPointClick={onPointClick} className={isFirstLevel ? 'pointer-on-bars' : ''} dataSource={data} > <Series type="bar" /> <ValueAxis showZero={false} /> <Legend visible={false} /> </Chart> <Button className="button-container" text="Back" icon="chevronleft" visible={!isFirstLevel} onClick={onButtonClick} /> </div> ); } export default App;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('app'), );
const data = [ { arg: 'Asia', val: 3007613498, parentID: '' }, { arg: 'North America', val: 493603615, parentID: '' }, { arg: 'Europe', val: 438575293, parentID: '' }, { arg: 'Africa', val: 381331438, parentID: '' }, { arg: 'South America', val: 331126555, parentID: '' }, { arg: 'Nigeria', val: 181562056, parentID: 'Africa' }, { arg: 'Egypt', val: 88487396, parentID: 'Africa' }, { arg: 'Congo', val: 77433744, parentID: 'Africa' }, { arg: 'Morocco', val: 33848242, parentID: 'Africa' }, { arg: 'China', val: 1380083000, parentID: 'Asia' }, { arg: 'India', val: 1306687000, parentID: 'Asia' }, { arg: 'Pakistan', val: 193885498, parentID: 'Asia' }, { arg: 'Japan', val: 126958000, parentID: 'Asia' }, { arg: 'Russia', val: 146804372, parentID: 'Europe' }, { arg: 'Germany', val: 82175684, parentID: 'Europe' }, { arg: 'Turkey', val: 79463663, parentID: 'Europe' }, { arg: 'France', val: 66736000, parentID: 'Europe' }, { arg: 'United Kingdom', val: 63395574, parentID: 'Europe' }, { arg: 'United States', val: 325310275, parentID: 'North America' }, { arg: 'Mexico', val: 121005815, parentID: 'North America' }, { arg: 'Canada', val: 36048521, parentID: 'North America' }, { arg: 'Cuba', val: 11239004, parentID: 'North America' }, { arg: 'Brazil', val: 205737996, parentID: 'South America' }, { arg: 'Colombia', val: 48400388, parentID: 'South America' }, { arg: 'Venezuela', val: 30761000, parentID: 'South America' }, { arg: 'Peru', val: 28220764, parentID: 'South America' }, { arg: 'Chile', val: 18006407, parentID: 'South America' }, ]; export default { filterData(name: string) { return data.filter((item) => item.parentID === name); }, };
window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, 'openai': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign', 'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs', 'devextreme-react': 'npm:devextreme-react@link:../../packages/devextreme-react/npm/cjs', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.5/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.54/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.12', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // SystemJS plugins 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: 'json', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.12/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('app'));
const data = [ { arg: 'Asia', val: 3007613498, parentID: '' }, { arg: 'North America', val: 493603615, parentID: '' }, { arg: 'Europe', val: 438575293, parentID: '' }, { arg: 'Africa', val: 381331438, parentID: '' }, { arg: 'South America', val: 331126555, parentID: '' }, { arg: 'Nigeria', val: 181562056, parentID: 'Africa' }, { arg: 'Egypt', val: 88487396, parentID: 'Africa' }, { arg: 'Congo', val: 77433744, parentID: 'Africa' }, { arg: 'Morocco', val: 33848242, parentID: 'Africa' }, { arg: 'China', val: 1380083000, parentID: 'Asia' }, { arg: 'India', val: 1306687000, parentID: 'Asia' }, { arg: 'Pakistan', val: 193885498, parentID: 'Asia' }, { arg: 'Japan', val: 126958000, parentID: 'Asia' }, { arg: 'Russia', val: 146804372, parentID: 'Europe' }, { arg: 'Germany', val: 82175684, parentID: 'Europe' }, { arg: 'Turkey', val: 79463663, parentID: 'Europe' }, { arg: 'France', val: 66736000, parentID: 'Europe' }, { arg: 'United Kingdom', val: 63395574, parentID: 'Europe' }, { arg: 'United States', val: 325310275, parentID: 'North America' }, { arg: 'Mexico', val: 121005815, parentID: 'North America' }, { arg: 'Canada', val: 36048521, parentID: 'North America' }, { arg: 'Cuba', val: 11239004, parentID: 'North America' }, { arg: 'Brazil', val: 205737996, parentID: 'South America' }, { arg: 'Colombia', val: 48400388, parentID: 'South America' }, { arg: 'Venezuela', val: 30761000, parentID: 'South America' }, { arg: 'Peru', val: 28220764, parentID: 'South America' }, { arg: 'Chile', val: 18006407, parentID: 'South America' }, ]; export default { filterData(name) { return data.filter((item) => item.parentID === name); }, };
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.2.3/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import("./index.tsx"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
#chart { height: 440px; width: 100%; } #chart.pointer-on-bars .dxc-series rect { cursor: pointer; } .button-container { text-align: center; height: 40px; position: absolute; top: 7px; left: 0; }

Bind to Data

This demo binds the drill-down chart to an array of objects. To visualize hierarchical data in the drill-down chart, filter the data source by the parentID for different drill-down views in the filterData function.

Implement View Navigation

To navigate from the main view to a detailed view, filter the data source by a different parentID in the onPointClick event handler. To navigate back, click the Back button. This action resets the data source filter. Use the isFirstLevel flag to distinguish views.

Customize the Appearance

Use the customizePoint function to change the individual point properties. This function returns an object with properties that should be changed for a certain point. In this demo, this function changes the color and hoverStyle properties.