If you have technical questions, please create a support ticket in the DevExpress Support Center.
import React, { useCallback, useState } from 'react';
import NumberBox, { NumberBoxTypes } from 'devextreme-react/number-box';
import {
Chart,
CommonSeriesSettings,
Series,
Point,
ArgumentAxis,
ValueAxis,
Legend,
} from 'devextreme-react/chart';
import { generateDataSource, customPositionLabel, offsetLabel } from './data.ts';
const dataSource = generateDataSource();
const defaultVisualRange = [-20, 20];
function App() {
const [argumentCustomPosition, setArgumentCustomPosition] = useState(0);
const [argumentOffset, setArgumentOffset] = useState(0);
const [valueCustomPosition, setValueCustomPosition] = useState(0);
const [valueOffset, setValueOffset] = useState(0);
const changeArgumentPosition = useCallback((e: NumberBoxTypes.ValueChangedEvent) => {
setArgumentCustomPosition(e.value);
}, [setArgumentCustomPosition]);
const changeArgumentOffset = useCallback((e: NumberBoxTypes.ValueChangedEvent) => {
setArgumentOffset(e.value);
}, [setArgumentOffset]);
const changeValuePosition = useCallback((e: NumberBoxTypes.ValueChangedEvent) => {
setValueCustomPosition(e.value);
}, [setValueCustomPosition]);
const changeValueOffset = useCallback((e: NumberBoxTypes.ValueChangedEvent) => {
setValueOffset(e.value);
}, [setValueOffset]);
return (
<div>
<Chart id="chart" dataSource={dataSource}>
<CommonSeriesSettings type='scatter' />
<Series argumentField='x1' valueField='y1' />
<Series argumentField='x2' valueField='y2'>
<Point symbol='triangleDown' />
</Series>
<ArgumentAxis
defaultVisualRange={defaultVisualRange}
customPosition={argumentCustomPosition}
offset={argumentOffset}
/>
<ValueAxis
defaultVisualRange={defaultVisualRange}
customPosition={valueCustomPosition}
offset={valueOffset}
endOnTick={false}
/>
<Legend visible={false} />
</Chart>
<div className='options'>
<div className='caption'>Options</div>
<div className="common">
<div className='block left'>
<span>Argument Axis</span>
<div className='option'>
<span>Custom position:</span>
<NumberBox
value={argumentCustomPosition}
showSpinButtons={true}
inputAttr={customPositionLabel}
onValueChanged={changeArgumentPosition} />
</div>
<div className='option'>
<span>Offset:</span>
<NumberBox
value={argumentOffset}
showSpinButtons={true}
inputAttr={offsetLabel}
onValueChanged={changeArgumentOffset} />
</div>
</div>
<div className='block right'>
<span>Value Axis</span>
<div className='option'>
<span>Custom position:</span>
<NumberBox
value={valueCustomPosition}
showSpinButtons={true}
inputAttr={customPositionLabel}
onValueChanged={changeValuePosition} />
</div>
<div className='option'>
<span>Offset:</span>
<NumberBox
value={valueOffset}
showSpinButtons={true}
inputAttr={offsetLabel}
onValueChanged={changeValueOffset} />
</div>
</div>
</div>
</div>
</div>
);
}
export default App;
xxxxxxxxxx
import React, { useCallback, useState } from 'react';
import NumberBox from 'devextreme-react/number-box';
import {
Chart,
CommonSeriesSettings,
Series,
Point,
ArgumentAxis,
ValueAxis,
Legend,
} from 'devextreme-react/chart';
import { generateDataSource, customPositionLabel, offsetLabel } from './data.js';
const dataSource = generateDataSource();
const defaultVisualRange = [-20, 20];
function App() {
const [argumentCustomPosition, setArgumentCustomPosition] = useState(0);
const [argumentOffset, setArgumentOffset] = useState(0);
const [valueCustomPosition, setValueCustomPosition] = useState(0);
const [valueOffset, setValueOffset] = useState(0);
const changeArgumentPosition = useCallback(
(e) => {
setArgumentCustomPosition(e.value);
},
[setArgumentCustomPosition],
);
const changeArgumentOffset = useCallback(
(e) => {
setArgumentOffset(e.value);
},
[setArgumentOffset],
);
const changeValuePosition = useCallback(
(e) => {
setValueCustomPosition(e.value);
},
[setValueCustomPosition],
);
const changeValueOffset = useCallback(
(e) => {
setValueOffset(e.value);
},
[setValueOffset],
);
return (
<div>
<Chart
id="chart"
dataSource={dataSource}
>
<CommonSeriesSettings type="scatter" />
<Series
argumentField="x1"
valueField="y1"
/>
<Series
argumentField="x2"
valueField="y2"
>
<Point symbol="triangleDown" />
</Series>
<ArgumentAxis
defaultVisualRange={defaultVisualRange}
customPosition={argumentCustomPosition}
offset={argumentOffset}
/>
<ValueAxis
defaultVisualRange={defaultVisualRange}
customPosition={valueCustomPosition}
offset={valueOffset}
endOnTick={false}
/>
<Legend visible={false} />
</Chart>
<div className="options">
<div className="caption">Options</div>
<div className="common">
<div className="block left">
<span>Argument Axis</span>
<div className="option">
<span>Custom position:</span>
<NumberBox
value={argumentCustomPosition}
showSpinButtons={true}
inputAttr={customPositionLabel}
onValueChanged={changeArgumentPosition}
/>
</div>
<div className="option">
<span>Offset:</span>
<NumberBox
value={argumentOffset}
showSpinButtons={true}
inputAttr={offsetLabel}
onValueChanged={changeArgumentOffset}
/>
</div>
</div>
<div className="block right">
<span>Value Axis</span>
<div className="option">
<span>Custom position:</span>
<NumberBox
value={valueCustomPosition}
showSpinButtons={true}
inputAttr={customPositionLabel}
onValueChanged={changeValuePosition}
/>
</div>
<div className="option">
<span>Offset:</span>
<NumberBox
value={valueOffset}
showSpinButtons={true}
inputAttr={offsetLabel}
onValueChanged={changeValueOffset}
/>
</div>
</div>
</div>
</div>
</div>
);
}
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 function generateDataSource() {
let x1; let x2; let y1; let y2; let
i;
const ds = [];
for (i = 0; i < 20; i += 1) {
x1 = random(5, 15);
y1 = random(5, 15);
ds.push({
x1, y1, x2, y2,
});
}
for (i = 0; i < 20; i += 1) {
x2 = random(5, 15);
y2 = random(-15, -5);
ds.push({
x1, y1, x2, y2,
});
}
for (i = 0; i < 20; i += 1) {
x2 = random(-15, -5);
y2 = random(5, 15);
ds.push({
x1, y1, x2, y2,
});
}
for (i = 0; i < 20; i += 1) {
x1 = random(-15, -5);
y1 = random(-15, -5);
ds.push({
x1, y1, x2, y2,
});
}
return ds;
}
function random(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export const customPositionLabel = { 'aria-label': 'Custom Position' };
export const offsetLabel = { 'aria-label': 'Offset' };
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@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);
xxxxxxxxxx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(<App />, document.getElementById('app'));
xxxxxxxxxx
export function generateDataSource() {
let x1;
let x2;
let y1;
let y2;
let i;
const ds = [];
for (i = 0; i < 20; i += 1) {
x1 = random(5, 15);
y1 = random(5, 15);
ds.push({
x1,
y1,
x2,
y2,
});
}
for (i = 0; i < 20; i += 1) {
x2 = random(5, 15);
y2 = random(-15, -5);
ds.push({
x1,
y1,
x2,
y2,
});
}
for (i = 0; i < 20; i += 1) {
x2 = random(-15, -5);
y2 = random(5, 15);
ds.push({
x1,
y1,
x2,
y2,
});
}
for (i = 0; i < 20; i += 1) {
x1 = random(-15, -5);
y1 = random(-15, -5);
ds.push({
x1,
y1,
x2,
y2,
});
}
return ds;
}
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export const customPositionLabel = { 'aria-label': 'Custom Position' };
export const offsetLabel = { 'aria-label': 'Offset' };
xxxxxxxxxx
<html lang="en">
<head></head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
xxxxxxxxxx
.options {
padding: 20px;
margin-top: 20px;
background-color: rgba(191, 191, 191, 0.15);
}
.option {
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.option > span {
margin-right: 20px;
}
.caption {
font-size: 18px;
font-weight: 700;
}
.option > .dx-numberbox {
width: 90px;
margin-left: auto;
}
.common {
width: 488px;
}
.block {
vertical-align: middle;
margin-top: 10px;
}
.left {
display: inline-block;
}
.right {
float: right;
}
.block > span {
font-size: 18px;
font-weight: 500;
}
Regardless of which automatic axis layout type you use, the Chart Control allows you to apply manual offsets. Specify the offset property in pixels to keep the axis position unchanged when users scroll or zoom the Chart data. The offset property shifts the axis from the specified position as follows:
-
If the axis is horizontal, a negative offset shifts the axis to the top, a positive offset shifts it to the bottom.
-
If the axis is vertical, a negative offset shifts the axis to the left, a positive offset shifts it to the right.
In this demo, you can use controls under the Chart to change the customPosition and offset properties for both axes.