DevExtreme v24.1 is now available.

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

Your search did not match any results.

React Charts - Custom Annotations

Annotations are designed to show additional information about visualized data. You can display images, text blocks, and custom content in annotations. To create custom annotations, specify the following Chart properties.

en.wikipedia.org
Backend API
import React from 'react'; import Chart, { Series, Annotation, Legend, CommonAnnotationSettings, } from 'devextreme-react/chart'; import { populationData } from './data.ts'; import AnnotationTemplate from './AnnotationTemplate.tsx'; function App() { return ( <Chart id="chart" dataSource={populationData} title="Top 5 Most Populated States in US" > <Series type="bar" argumentField="name" valueField="population" name="Population" > </Series> <CommonAnnotationSettings type="custom" series="Population" render={AnnotationTemplate} allowDragging={true} > </CommonAnnotationSettings> {populationData.map((data) => ( <Annotation argument={data.name} key={data.name} data={data} > </Annotation> ))} <Legend visible={false}></Legend> </Chart> ); } export default App;
import React from 'react'; import Chart, { Series, Annotation, Legend, CommonAnnotationSettings, } from 'devextreme-react/chart'; import { populationData } from './data.js'; import AnnotationTemplate from './AnnotationTemplate.js'; function App() { return ( <Chart id="chart" dataSource={populationData} title="Top 5 Most Populated States in US" > <Series type="bar" argumentField="name" valueField="population" name="Population" ></Series> <CommonAnnotationSettings type="custom" series="Population" render={AnnotationTemplate} allowDragging={true} ></CommonAnnotationSettings> {populationData.map((data) => ( <Annotation argument={data.name} key={data.name} data={data} ></Annotation> ))} <Legend visible={false}></Legend> </Chart> ); } export default App;
import React from 'react'; function getImagePath(data: { name: string; }) { return `../../../../images/flags/${data.name.replace(/\s/, '')}.svg`; } const formatNumber = new Intl.NumberFormat('en-US', { minimumFractionDigits: 0, }).format; interface AnnotationData { name: string, capital: string, population: number, area: number, } export default function AnnotationTemplate(annotation: { argument?: string; data?: AnnotationData; }) { const { data } = annotation; return ( <svg className="annotation"> <image href={getImagePath(data)} width="60" height="40" /> <rect x={0} y={0} className="border"></rect> <text x="70" y="25" className="state">{annotation.argument}</text> <text x="0" y="60"> <tspan className="caption">Capital:</tspan> <tspan className="capital" dx="5">{data.capital}</tspan> <tspan dy="14" x="0" className="caption">Population:</tspan> <tspan className="population" dx="5">{formatNumber(data.population)}</tspan> <tspan dy="14" x="0" className="caption">Area:</tspan> <tspan className="area" dx="5">{formatNumber(data.area)}</tspan> <tspan dx="5">km</tspan> <tspan className="sup" dy="-2">2</tspan> </text> </svg> ); }
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('app'), );
export const populationData = [{ name: 'California', population: 38802500, capital: 'Sacramento', area: 423967, }, { name: 'Texas', population: 26956958, capital: 'Austin', area: 695662, }, { name: 'Florida', population: 19893297, capital: 'Tallahassee', area: 170312, }, { name: 'New York', population: 19746227, capital: 'Albany', area: 141297, }, { name: 'Illinois', population: 12880580, capital: 'Springfield', area: 149995, }];
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, }, }, paths: { 'npm:': 'https://unpkg.com/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.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@15.8.1/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'devextreme': 'npm:devextreme@24.1.6/cjs', 'devextreme-react': 'npm:devextreme-react@24.1.6/cjs', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.11/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.56/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.13', '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@7.4.11/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.13/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
import React from 'react'; function getImagePath(data) { return `../../../../images/flags/${data.name.replace(/\s/, '')}.svg`; } const formatNumber = new Intl.NumberFormat('en-US', { minimumFractionDigits: 0, }).format; export default function AnnotationTemplate(annotation) { const { data } = annotation; return ( <svg className="annotation"> <image href={getImagePath(data)} width="60" height="40" /> <rect x={0} y={0} className="border" ></rect> <text x="70" y="25" className="state" > {annotation.argument} </text> <text x="0" y="60" > <tspan className="caption">Capital:</tspan> <tspan className="capital" dx="5" > {data.capital} </tspan> <tspan dy="14" x="0" className="caption" > Population: </tspan> <tspan className="population" dx="5" > {formatNumber(data.population)} </tspan> <tspan dy="14" x="0" className="caption" > Area: </tspan> <tspan className="area" dx="5" > {formatNumber(data.area)} </tspan> <tspan dx="5">km</tspan> <tspan className="sup" dy="-2" > 2 </tspan> </text> </svg> ); }
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('app'));
export const populationData = [ { name: 'California', population: 38802500, capital: 'Sacramento', area: 423967, }, { name: 'Texas', population: 26956958, capital: 'Austin', area: 695662, }, { name: 'Florida', population: 19893297, capital: 'Tallahassee', area: 170312, }, { name: 'New York', population: 19746227, capital: 'Albany', area: 141297, }, { name: 'Illinois', population: 12880580, capital: 'Springfield', area: 149995, }, ];
<!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.1.6/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; } .annotation { font-size: 12px; } .border { width: 60px; height: 40px; stroke: rgba(191, 191, 191, 0.25); stroke-width: 1px; fill: transparent; } .state { font-weight: 500; font-size: 14px; } .caption { font-weight: 500; } .sup { font-size: 0.8em; }

annotations[]

The annotations array specifies a collection of annotations. This demo uses a data source array to create this collection. Each object has the argument property and a data object. The argument property positions an annotation at a specific argument, and the data object stores data from a data source.

You can use the annotations array to configure each annotation individually.

commonAnnotationSettings

Use the commonAnnotationSettings object to specify settings common to all annotations in the chart. In this object, assign custom to the type property. To anchor annotations to a series point, specify the series property. Finally, declare the SVG markup in the annotation template.

Note that individual annotation settings override the common annotation settings.

customizeAnnotation

You can also implement the customizeAnnotation function to customize an individual chart annotation.