Backend API
import React, { useCallback, useRef } from 'react';
import {
Chart,
CommonSeriesSettings,
Series,
Legend,
Title,
Subtitle,
} from 'devextreme-react/chart';
import type { ChartRef } from 'devextreme-react/chart';
import { Button } from 'devextreme-react/button';
import { exportFromMarkup } from 'devextreme/viz/export';
import { Canvg } from 'canvg';
import { dataSource } from './data.ts';
import Form from './Form.tsx';
const barPadding = 0.3;
const prepareMarkup = (chartSVG: string, sourceContainer: HTMLElement) => {
const sourceElements = sourceContainer.querySelectorAll('*');
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
svg.setAttribute('version', '1.1');
svg.setAttribute('width', '820px');
svg.setAttribute('height', '420px');
const clonedContainer = sourceContainer.cloneNode(true) as HTMLElement;
const clonedElements = clonedContainer.querySelectorAll('*');
clonedElements.forEach((clonedEl, index) => {
const sourceEl = sourceElements[index] as HTMLElement;
if (!sourceEl) return;
const computed = window.getComputedStyle(sourceEl);
['fill', 'font-family', 'font-size', 'font-weight', 'opacity', 'stroke', 'stroke-width'].forEach((prop) => {
const value = computed.getPropertyValue(prop)?.trim();
if (value && value !== 'auto' && value !== 'normal' && value !== 'initial') {
(clonedEl as SVGElement).setAttribute(prop, value);
}
});
});
svg.innerHTML = clonedContainer.innerHTML;
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
group.setAttribute('transform', 'translate(305,12)');
group.innerHTML = chartSVG;
svg.appendChild(group);
return svg;
};
function App() {
const childRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<ChartRef>(null);
const onClick = useCallback(() => {
exportFromMarkup(
prepareMarkup(chartRef.current?.instance().svg() ?? '',
childRef.current ?? document.createElement('div')), {
width: 820,
height: 420,
margin: 0,
format: 'png',
svgToCanvas(svg: Node, canvas: HTMLCanvasElement) {
return new Promise((resolve): void => {
const ctx = canvas.getContext('2d');
if (!ctx) {
resolve(() => {});
return;
}
const v = Canvg.fromString(
ctx,
new XMLSerializer().serializeToString(svg)
);
resolve(v.render());
});
},
},
);
}, []);
return (
<div id="chart-demo">
<div className="chart-environment">
<Form ref={childRef} />
<Chart
ref={chartRef}
id="chart"
dataSource={dataSource}
palette="Violet"
>
<CommonSeriesSettings barPadding={barPadding} argumentField="state" type="bar" />
<Series valueField="year2000" name="2000" />
<Series valueField="year2010" name="2010" />
<Series valueField="year2020" name="2020" />
<Series valueField="year2021" name="2021" />
<Series valueField="year2022" name="2022" />
<Legend verticalAlignment="bottom" horizontalAlignment="center" />
<Title text="Oil Production">
<Subtitle text="(in millions tonnes)" />
</Title>
</Chart>
</div>
<div className="controls-pane">
<Button
id="export"
width={145}
icon="export"
text="Export"
type="default"
onClick={onClick}
/>
</div>
</div>
);
}
export default App;
import React, { useCallback, useRef } from 'react';
import {
Chart,
CommonSeriesSettings,
Series,
Legend,
Title,
Subtitle,
} from 'devextreme-react/chart';
import { Button } from 'devextreme-react/button';
import { exportFromMarkup } from 'devextreme/viz/export';
import { Canvg } from 'canvg';
import { dataSource } from './data.js';
import Form from './Form.js';
const barPadding = 0.3;
const prepareMarkup = (chartSVG, sourceContainer) => {
const sourceElements = sourceContainer.querySelectorAll('*');
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
svg.setAttribute('version', '1.1');
svg.setAttribute('width', '820px');
svg.setAttribute('height', '420px');
const clonedContainer = sourceContainer.cloneNode(true);
const clonedElements = clonedContainer.querySelectorAll('*');
clonedElements.forEach((clonedEl, index) => {
const sourceEl = sourceElements[index];
if (!sourceEl) return;
const computed = window.getComputedStyle(sourceEl);
[
'fill',
'font-family',
'font-size',
'font-weight',
'opacity',
'stroke',
'stroke-width',
].forEach((prop) => {
const value = computed.getPropertyValue(prop)?.trim();
if (value && value !== 'auto' && value !== 'normal' && value !== 'initial') {
clonedEl.setAttribute(prop, value);
}
});
});
svg.innerHTML = clonedContainer.innerHTML;
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
group.setAttribute('transform', 'translate(305,12)');
group.innerHTML = chartSVG;
svg.appendChild(group);
return svg;
};
function App() {
const childRef = useRef(null);
const chartRef = useRef(null);
const onClick = useCallback(() => {
exportFromMarkup(
prepareMarkup(
chartRef.current?.instance().svg() ?? '',
childRef.current ?? document.createElement('div'),
),
{
width: 820,
height: 420,
margin: 0,
format: 'png',
svgToCanvas(svg, canvas) {
return new Promise((resolve) => {
const ctx = canvas.getContext('2d');
if (!ctx) {
resolve(() => {});
return;
}
const v = Canvg.fromString(ctx, new XMLSerializer().serializeToString(svg));
resolve(v.render());
});
},
},
);
}, []);
return (
<div id="chart-demo">
<div className="chart-environment">
<Form ref={childRef} />
<Chart
ref={chartRef}
id="chart"
dataSource={dataSource}
palette="Violet"
>
<CommonSeriesSettings
barPadding={barPadding}
argumentField="state"
type="bar"
/>
<Series
valueField="year2000"
name="2000"
/>
<Series
valueField="year2010"
name="2010"
/>
<Series
valueField="year2020"
name="2020"
/>
<Series
valueField="year2021"
name="2021"
/>
<Series
valueField="year2022"
name="2022"
/>
<Legend
verticalAlignment="bottom"
horizontalAlignment="center"
/>
<Title text="Oil Production">
<Subtitle text="(in millions tonnes)" />
</Title>
</Chart>
</div>
<div className="controls-pane">
<Button
id="export"
width={145}
icon="export"
text="Export"
type="default"
onClick={onClick}
/>
</div>
</div>
);
}
export default App;
import React from 'react';
const Form = React.forwardRef<HTMLDivElement>((_, ref) => (
<div id="custom-markup-container" ref={ref}>
<svg width="820px" height="420px">
<path opacity="1" d="M 0 0 L 820 0 L 820 420 L 0 420 L 0 0" stroke="#999999" strokeWidth="1"
strokeLinecap="butt" fill="white" strokeLinejoin="miter"></path>
<path d="M 13 407 L 128 407 L 232 39 L 13 39" fill="#6D39C3"></path>
<path d="M 46 381 L 161 381 L 265 13 L 46 13" opacity="0.5" fill="#6D39C3"></path>
<text transform="translate(30,89)" className="custom-markup-text custom-markup-text-title">
<tspan x="0" y="0">Export </tspan>
<tspan x="0" y="38">Custom</tspan>
<tspan x="0" y="76">Markup</tspan>
</text>
<text transform="translate(32,199)" className="custom-markup-text custom-markup-text-subtitle">
<tspan x="0" y="0">Export a chart with</tspan>
<tspan x="0" y="19.2">custom elements</tspan>
</text>
</svg>
</div>
));
Form.displayName = 'Form';
export default Form;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.tsx';
ReactDOM.render(
<App />,
document.getElementById('app'),
);
export const dataSource = [{
state: 'Saudi Arabia',
year2000: 408,
year2010: 407,
year2020: 498,
year2021: 515,
year2022: 602,
}, {
state: 'USA',
year2000: 288,
year2010: 270,
year2020: 713,
year2021: 711,
year2022: 762,
}, {
state: 'Mexico',
year2000: 150,
year2010: 146,
year2020: 101,
year2021: 97,
year2022: 95,
}];
import React from 'react';
const Form = React.forwardRef((_, ref) => (
<div
id="custom-markup-container"
ref={ref}
>
<svg
width="820px"
height="420px"
>
<path
opacity="1"
d="M 0 0 L 820 0 L 820 420 L 0 420 L 0 0"
stroke="#999999"
strokeWidth="1"
strokeLinecap="butt"
fill="white"
strokeLinejoin="miter"
></path>
<path
d="M 13 407 L 128 407 L 232 39 L 13 39"
fill="#6D39C3"
></path>
<path
d="M 46 381 L 161 381 L 265 13 L 46 13"
opacity="0.5"
fill="#6D39C3"
></path>
<text
transform="translate(30,89)"
className="custom-markup-text custom-markup-text-title"
>
<tspan
x="0"
y="0"
>
Export{' '}
</tspan>
<tspan
x="0"
y="38"
>
Custom
</tspan>
<tspan
x="0"
y="76"
>
Markup
</tspan>
</text>
<text
transform="translate(32,199)"
className="custom-markup-text custom-markup-text-subtitle"
>
<tspan
x="0"
y="0"
>
Export a chart with
</tspan>
<tspan
x="0"
y="19.2"
>
custom elements
</tspan>
</text>
</svg>
</div>
));
Form.displayName = 'Form';
export default Form;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(<App />, document.getElementById('app'));
export const dataSource = [
{
state: 'Saudi Arabia',
year2000: 408,
year2010: 407,
year2020: 498,
year2021: 515,
year2022: 602,
},
{
state: 'USA',
year2000: 288,
year2010: 270,
year2020: 713,
year2021: 711,
year2022: 762,
},
{
state: 'Mexico',
year2000: 150,
year2010: 146,
year2020: 101,
year2021: 97,
year2022: 95,
},
];
<!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/26.1.5/css/dx.light.css" />
<link rel="stylesheet" type="text/css" href="./bundle.css" />
</head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
<script src="bundles/vendor/react.vendor.js"></script>
<script src="./bundle.js"></script>
</body>
</html>
#chart-demo {
height: 460px;
}
#chart {
position: absolute;
top: 12px;
right: 35px;
width: 480px;
height: 347px;
}
.chart-environment {
width: 820px;
position: relative;
margin: 0 auto;
}
.controls-pane {
text-align: center;
}
.custom-markup-text {
fill: #fff;
font-family: 'Segoe UI', 'Helvetica Neue', 'Trebuchet MS', Verdana, sans-serif;
}
.custom-markup-text-title {
font-size: 36px;
font-weight: bold;
}
.custom-markup-text-subtitle {
font-size: 14px;
opacity: 0.8;
}