Feel free to share demo-related thoughts here.
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Thank you for the feedback!
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Backend API
<template>
<DxVectorMap
id="vector-map"
:bounds="bounds"
>
<DxLayer
:data-source="usa"
/>
<DxCommonAnnotationSettings
type="custom"
template="annotationTemplate"
/>
<DxAnnotation
v-for="state in statesData"
:coordinates="state.coordinates"
:offset-x="state.offsetX"
:offset-y="state.offsetY"
:data="state.data"
:key="state.data.name"
/>
<template #annotationTemplate="{ data }">
<AnnotationTemplate :annotation="data"/>
</template>
</DxVectorMap>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import * as mapsData from 'devextreme-dist/js/vectormap-data/usa.js';
import {
DxVectorMap,
DxLayer,
DxAnnotation,
DxCommonAnnotationSettings,
} from 'devextreme-vue/vector-map';
import { statesData } from './data.ts';
import AnnotationTemplate from './AnnotationTemplate.vue';
const usa = ref(mapsData.usa);
const bounds = ref([-118, 55, -80, 23]);
</script>
<style>
#vector-map {
height: 440px;
}
</style>
<template>
<svg class="annotation">
<image
:href="getImagePath()"
width="60"
height="40"
/>
<rect
x="0"
y="0"
class="border"
/>
<text
x="70"
y="25"
class="state"
>{{ data.name }}</text>
<text
x="0"
y="60"
>
<tspan class="caption">Capital:</tspan><tspan
class="capital"
dx="5"
>{{ data.capital }}</tspan><tspan
dy="14"
x="0"
class="caption"
>Population:</tspan><tspan
class="population"
dx="5"
>{{ formatNumber(data.population) }}</tspan><tspan
dy="14"
x="0"
class="caption"
>Area:</tspan><tspan
class="area"
dx="5"
>{{ formatNumber(data.area) }}</tspan><tspan dx="5">km</tspan><tspan
class="sup"
dy="-2"
>2</tspan>
</text>
</svg>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const props = withDefaults(defineProps<{
annotation?: Record<'data', Record<string, unknown>>
}>(), {
annotation: () => ({}),
});
const data = ref(props.annotation.data);
const getImagePath = () => `../../../../images/flags/${data.value.name.replace(/\s/, '')}.svg`;
const formatNumber = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 0,
}).format;
</script>
<style>
.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;
}
</style>
window.exports = window.exports || {};
window.config = {
transpiler: 'plugin-babel',
meta: {
'*.vue': {
loader: 'vue-loader',
},
'*.ts': {
loader: 'demo-ts-loader',
},
'*.svg': {
loader: 'svg-loader',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
'devextreme-dist/js/vectormap-data/*': {
'esModule': true,
},
},
paths: {
'root:': '../../../../',
'npm:': 'https://unpkg.com/',
},
map: {
'vue': 'npm:vue@3.2.47/dist/vue.esm-browser.js',
'vue-loader': 'npm:dx-systemjs-vue-browser@1.1.1/index.js',
'demo-ts-loader': 'root:utils/demo-ts-loader.js',
'svg-loader': 'root:utils/svg-loader.js',
'devextreme-dist/js/vectormap-data': 'npm:devextreme-dist@24.1.6/js/vectormap-data',
'mitt': 'npm:mitt/dist/mitt.umd.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-vue': 'npm:devextreme-vue@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',
'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-vue': {
main: 'index.js',
},
'devextreme': {
defaultExtension: 'js',
},
'devextreme/events/utils': {
main: 'index',
},
'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,
},
};
System.config(window.config);
export const statesData = [{
coordinates: [-75.4999, 43.00035],
data: {
name: 'New York',
population: 19746227,
capital: 'Albany',
area: 141297,
},
}, {
coordinates: [-89, 40],
offsetX: -100,
offsetY: -80,
data: {
name: 'Illinois',
population: 12880580,
capital: 'Springfield',
area: 149995,
},
}, {
coordinates: [-81.760254, 27.994402],
data: {
name: 'Florida',
population: 19893297,
capital: 'Tallahassee',
area: 170312,
},
}, {
coordinates: [-100, 31],
data: {
name: 'Texas',
population: 26956958,
capital: 'Austin',
area: 695662,
},
}, {
coordinates: [-119.417931, 36.778259],
data: {
name: 'California',
population: 38802500,
capital: 'Sacramento',
area: 423967,
},
}];
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
<!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" />
<script src="https://unpkg.com/typescript@4.9.5/lib/typescript.js"></script>
<script type="module">
import * as vueCompilerSFC from "https://unpkg.com/@vue/compiler-sfc@3.4.16/dist/compiler-sfc.esm-browser.js";
window.vueCompilerSFC = vueCompilerSFC;
</script>
<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.ts");
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
You can set each annotation type property to "text", "image", or "custom". In this demo, annotation type has been set to "custom".
Custom annotations require that you specify your own display template in SVG format. As you can see in the demo code, you can access annotation data within template markup.
For more information on annotation settings, refer to the annotations[] help topic.