If you have technical questions, please create a support ticket in the DevExpress Support Center.
import React, { useCallback, useState } from 'react';
import TreeMap, {
Colorizer, Tooltip, ITreeMapOptions, ITooltipProps,
} from 'devextreme-react/tree-map';
import { TreeMapLayoutAlgorithm } from 'devextreme/viz/tree_map';
import SelectBox, { SelectBoxTypes } from 'devextreme-react/select-box';
import { populationByAge, algorithmLabel } from './data.ts';
const algorithms: (TreeMapLayoutAlgorithm | 'custom')[] = ['sliceanddice', 'squarified', 'strip', 'custom'];
function App() {
const [selectedAlgorithm, setSelectedAlgorithm] = useState(algorithms[2]);
const [
currentAlgorithm,
setCurrentAlgorithm,
] = useState<ITreeMapOptions['layoutAlgorithm']>(getCurrentAlgorithm(algorithms[2]));
const setAlgorithm = useCallback((data: SelectBoxTypes.ValueChangedEvent) => {
setSelectedAlgorithm(data.value);
setCurrentAlgorithm(() => getCurrentAlgorithm(data.value));
}, [setSelectedAlgorithm, setCurrentAlgorithm]);
return (
<React.Fragment>
<TreeMap
id="treemap"
dataSource={populationByAge}
layoutAlgorithm={currentAlgorithm}
title="Population by Age Groups"
>
<Colorizer
colorizeGroups={true}
type="discrete"
/>
<Tooltip
enabled={true}
customizeTooltip={customizeTooltip}
format="thousands"
/>
</TreeMap>
<div className="options">
<div className="caption">Options</div>
<div className="option">
<span>Tiling Algorithm </span>
<SelectBox
dataSource={algorithms}
width={200}
inputAttr={algorithmLabel}
value={selectedAlgorithm}
onValueChanged={setAlgorithm}
/>
</div>
</div>
</React.Fragment>
);
}
const customAlgorithm: ITreeMapOptions['layoutAlgorithm'] = (arg) => {
const totalRect = arg.rect.slice();
let totalSum = arg.sum;
let side = 0;
arg.items.forEach((item) => {
const size = Math.round(((totalRect[side + 2] - totalRect[side]) * item.value) / totalSum);
const rect = totalRect.slice();
totalSum -= item.value;
totalRect[side] += size;
rect[side + 2] = totalRect[side];
item.rect = rect;
side = 1 - side;
});
};
function getCurrentAlgorithm(selectedAlgorithm: (TreeMapLayoutAlgorithm | 'custom')): ITreeMapOptions['layoutAlgorithm'] {
let currentAlgorithm: ITreeMapOptions['layoutAlgorithm'] | 'custom' = selectedAlgorithm;
if (currentAlgorithm === 'custom') {
currentAlgorithm = customAlgorithm;
}
return currentAlgorithm;
}
const customizeTooltip: ITooltipProps['customizeTooltip'] = (arg) => {
const { data } = arg.node;
const parentData = arg.node.getParent().data;
return {
text: arg.node.isLeaf()
? `<span class='country'>${parentData.name}</span><br />${data.name}<br />${arg.valueText}(${((100 * data.value) / parentData.total).toFixed(1)}%)`
: `<span class='country'>${data.name}</span>`,
};
};
export default App;
xxxxxxxxxx
import React, { useCallback, useState } from 'react';
import TreeMap, { Colorizer, Tooltip } from 'devextreme-react/tree-map';
import SelectBox from 'devextreme-react/select-box';
import { populationByAge, algorithmLabel } from './data.js';
const algorithms = ['sliceanddice', 'squarified', 'strip', 'custom'];
function App() {
const [selectedAlgorithm, setSelectedAlgorithm] = useState(algorithms[2]);
const [currentAlgorithm, setCurrentAlgorithm] = useState(getCurrentAlgorithm(algorithms[2]));
const setAlgorithm = useCallback(
(data) => {
setSelectedAlgorithm(data.value);
setCurrentAlgorithm(() => getCurrentAlgorithm(data.value));
},
[setSelectedAlgorithm, setCurrentAlgorithm],
);
return (
<React.Fragment>
<TreeMap
id="treemap"
dataSource={populationByAge}
layoutAlgorithm={currentAlgorithm}
title="Population by Age Groups"
>
<Colorizer
colorizeGroups={true}
type="discrete"
/>
<Tooltip
enabled={true}
customizeTooltip={customizeTooltip}
format="thousands"
/>
</TreeMap>
<div className="options">
<div className="caption">Options</div>
<div className="option">
<span>Tiling Algorithm </span>
<SelectBox
dataSource={algorithms}
width={200}
inputAttr={algorithmLabel}
value={selectedAlgorithm}
onValueChanged={setAlgorithm}
/>
</div>
</div>
</React.Fragment>
);
}
const customAlgorithm = (arg) => {
const totalRect = arg.rect.slice();
let totalSum = arg.sum;
let side = 0;
arg.items.forEach((item) => {
const size = Math.round(((totalRect[side + 2] - totalRect[side]) * item.value) / totalSum);
const rect = totalRect.slice();
totalSum -= item.value;
totalRect[side] += size;
rect[side + 2] = totalRect[side];
item.rect = rect;
side = 1 - side;
});
};
function getCurrentAlgorithm(selectedAlgorithm) {
let currentAlgorithm = selectedAlgorithm;
if (currentAlgorithm === 'custom') {
currentAlgorithm = customAlgorithm;
}
return currentAlgorithm;
}
const customizeTooltip = (arg) => {
const { data } = arg.node;
const parentData = arg.node.getParent().data;
return {
text: arg.node.isLeaf()
? `<span class='country'>${parentData.name}</span><br />${data.name}<br />${arg.valueText}(${(
(100 * data.value)
/ parentData.total
).toFixed(1)}%)`
: `<span class='country'>${data.name}</span>`,
};
};
export default App;
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.tsx';
ReactDOM.render(
<App />,
document.getElementById('app'),
);
xxxxxxxxxx
export const populationByAge = [{
name: 'Brazil',
total: 200050486,
items: [{
name: 'Age 0-14',
value: 48058462,
}, {
name: 'Age 15-44',
value: 96036995,
}, {
name: 'Age 45-64',
value: 40715296,
}, {
name: 'Age 65+',
value: 15239733,
}],
}, {
name: 'Japan',
total: 126345237,
items: [{
name: 'Age 0-14',
value: 16593766,
}, {
name: 'Age 15-44',
value: 45455276,
}, {
name: 'Age 45-64',
value: 32845790,
}, {
name: 'Age 65+',
value: 31450405,
}],
}, {
name: 'United States',
total: 318497627,
items: [{
name: 'Age 0-14',
value: 63968935,
}, {
name: 'Age 15-44',
value: 127217843,
}, {
name: 'Age 45-64',
value: 83145484,
}, {
name: 'Age 65+',
value: 44165365,
}],
}];
export const algorithmLabel = { 'aria-label': 'Algorithm' };
xxxxxxxxxx
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@24.2.5/cjs',
'devextreme-react': 'npm:devextreme-react@24.2.5/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);
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(<App />, document.getElementById('app'));
xxxxxxxxxx
export const populationByAge = [
{
name: 'Brazil',
total: 200050486,
items: [
{
name: 'Age 0-14',
value: 48058462,
},
{
name: 'Age 15-44',
value: 96036995,
},
{
name: 'Age 45-64',
value: 40715296,
},
{
name: 'Age 65+',
value: 15239733,
},
],
},
{
name: 'Japan',
total: 126345237,
items: [
{
name: 'Age 0-14',
value: 16593766,
},
{
name: 'Age 15-44',
value: 45455276,
},
{
name: 'Age 45-64',
value: 32845790,
},
{
name: 'Age 65+',
value: 31450405,
},
],
},
{
name: 'United States',
total: 318497627,
items: [
{
name: 'Age 0-14',
value: 63968935,
},
{
name: 'Age 15-44',
value: 127217843,
},
{
name: 'Age 45-64',
value: 83145484,
},
{
name: 'Age 65+',
value: 44165365,
},
],
},
];
export const algorithmLabel = { 'aria-label': 'Algorithm' };
xxxxxxxxxx
<html lang="en">
<head></head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
xxxxxxxxxx
#treemap {
height: 460px;
}
.country {
font-weight: 500;
}
.options {
padding: 20px;
background-color: rgba(191, 191, 191, 0.15);
margin-top: 20px;
}
.option {
margin-top: 10px;
}
.caption {
font-size: 18px;
font-weight: 500;
}
.option > span {
margin-right: 10px;
}
.option > .dx-widget {
display: inline-block;
vertical-align: middle;
}
The TreeMap component supports three layout algorithms out of the box:
-
Squarified (default)
This algorithm lays the items out so that the aspect ratio will be closer to 1. In other words, this algorithm tries to make items as square as possible. -
Strip
This algorithm is a modification of the "Squarified" algorithm. At the beginning, the algorithm has an available area divided into several strips and a set of items to distribute between the strips. Throughout the layout process, the current strip is maintained. For each item to be arranged, the algorithm checks whether or not adding the item to the current strip improves the average aspect ratios of the rectangles in the current strip. If so, the item is added to the current strip. Otherwise, it is added to the next strip. -
Slice and Dice
This algorithm uses parallel lines to divide an available area into rectangles that visualize items. In a hierarchical structure, each rectangle that visualizes an item is further divided into smaller rectangles that visualize its children, and so on.
Use the layoutAlgorithm property to specify the tile layout. You can also implement your own algorithm. For this purpose, assign a function to the layoutAlgorithm property. This function calculates the coordinates of two diagonally-opposing points that define a rectangle and assigns them to the required item.