mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
Add the VectorMap plugin and move the vector map demos to it
- world map generated from Natural Earth, shipped as tabler-vector-map.js and maps/world.js - values, markers, lines, tooltip and zoom - demo, docs and screenshots no longer load jsVectorMap
This commit is contained in:
1 parent
360d0be7d5
commit
874897218f
43 files changed
+2654
-631
No files matched your search
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
/**
|
||||
* Generates the map files of the VectorMap plugin (`core/js/maps/*.ts`) from
|
||||
* Natural Earth (https://www.naturalearthdata.com, public domain).
|
||||
*
|
||||
* The shapes are projected here, at build time, and written as ready SVG path
|
||||
* data. The plugin only puts them into an `<svg>`, so no geo library ships to
|
||||
* the browser.
|
||||
*
|
||||
* Usage: pnpm run generate:maps
|
||||
*/
|
||||
|
||||
import { writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import * as prettier from 'prettier'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const outDir = join(__dirname, '..', 'core', 'js', 'maps')
|
||||
|
||||
// Pinned, so that a new run gives the same file.
|
||||
const SOURCE = 'https://raw.githubusercontent.com/nvkelso/natural-earth-vector/v5.1.2/geojson'
|
||||
|
||||
type Position = [number, number]
|
||||
type Ring = Position[]
|
||||
|
||||
interface Feature {
|
||||
properties: Record<string, string | number | null>
|
||||
geometry: { type: 'Polygon'; coordinates: Ring[] } | { type: 'MultiPolygon'; coordinates: Ring[][] }
|
||||
}
|
||||
|
||||
interface MapConfig {
|
||||
/** File name in `core/js/maps/` and the name the map is registered under */
|
||||
name: string
|
||||
/** Natural Earth file, without the extension */
|
||||
source: string
|
||||
/** Width of the viewBox; the height follows from the projection */
|
||||
width: number
|
||||
/** Region codes left out of the map */
|
||||
exclude: string[]
|
||||
}
|
||||
|
||||
const maps: MapConfig[] = [
|
||||
{
|
||||
name: 'world',
|
||||
source: 'ne_110m_admin_0_countries',
|
||||
width: 1000,
|
||||
exclude: ['AQ'],
|
||||
},
|
||||
]
|
||||
|
||||
// Territories with no ISO 3166-1 code in Natural Earth, keyed by `ADM0_A3`.
|
||||
// `XK` is the code Kosovo uses in practice; the other two are user-assigned.
|
||||
const CODE_OVERRIDES: Record<string, string> = {
|
||||
KOS: 'XK',
|
||||
CYN: 'XC',
|
||||
SOL: 'XS',
|
||||
}
|
||||
|
||||
// One decimal place in a 1000 unit wide viewBox is about 0.04° of longitude,
|
||||
// well below the detail of the 110m data.
|
||||
const PRECISION = 10
|
||||
|
||||
const RADIANS = Math.PI / 180
|
||||
|
||||
/**
|
||||
* Miller cylindrical projection, in radians. Placing a point on the map by its
|
||||
* coordinates takes the same formula, which is why `projection` is written
|
||||
* into the map file.
|
||||
*/
|
||||
const miller = ([lng, lat]: Position): Position => [lng * RADIANS, 1.25 * Math.log(Math.tan(Math.PI / 4 + 0.4 * lat * RADIANS))]
|
||||
|
||||
const regionCode = (properties: Feature['properties']): string | null => {
|
||||
const override = CODE_OVERRIDES[String(properties.ADM0_A3)]
|
||||
if (override) {
|
||||
return override
|
||||
}
|
||||
|
||||
// `ISO_A2` is "-99" for France and Norway; `ISO_A2_EH` has them right.
|
||||
for (const key of ['ISO_A2_EH', 'ISO_A2']) {
|
||||
const code = String(properties[key] ?? '')
|
||||
if (/^[A-Z]{2}$/.test(code)) {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const ringsOf = (geometry: Feature['geometry']): Ring[] => (geometry.type === 'Polygon' ? geometry.coordinates : geometry.coordinates.flat())
|
||||
|
||||
// "0.5" → ".5", "-0.5" → "-.5"
|
||||
const formatNumber = (tenths: number): string => String(tenths / PRECISION).replace(/^(-?)0\./, '$1.')
|
||||
|
||||
// A minus sign already separates two numbers, so a space is only needed in
|
||||
// front of a positive one.
|
||||
const joinNumbers = (numbers: number[]): string => {
|
||||
let out = ''
|
||||
for (const number of numbers) {
|
||||
const text = formatNumber(number)
|
||||
out += out && !text.startsWith('-') ? ` ${text}` : text
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* One ring as path data: an absolute move, then relative lines. The deltas are
|
||||
* taken between already rounded points, so rounding does not add up.
|
||||
*/
|
||||
const ringPath = (points: Position[]): string => {
|
||||
const rounded: Position[] = []
|
||||
for (const [x, y] of points) {
|
||||
const point: Position = [Math.round(x * PRECISION), Math.round(y * PRECISION)]
|
||||
const last = rounded.at(-1)
|
||||
if (!last || last[0] !== point[0] || last[1] !== point[1]) {
|
||||
rounded.push(point)
|
||||
}
|
||||
}
|
||||
|
||||
// GeoJSON repeats the first point at the end; `z` closes the ring instead.
|
||||
const first = rounded[0]
|
||||
const last = rounded.at(-1)
|
||||
if (rounded.length > 1 && first && last && first[0] === last[0] && first[1] === last[1]) {
|
||||
rounded.pop()
|
||||
}
|
||||
|
||||
if (rounded.length < 3) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const deltas: number[] = []
|
||||
for (let i = 1; i < rounded.length; i++) {
|
||||
deltas.push(rounded[i][0] - rounded[i - 1][0], rounded[i][1] - rounded[i - 1][1])
|
||||
}
|
||||
|
||||
return `M${joinNumbers(rounded[0])}l${joinNumbers(deltas)}z`
|
||||
}
|
||||
|
||||
async function generate(config: MapConfig): Promise<void> {
|
||||
const url = `${SOURCE}/${config.source}.geojson`
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Could not load ${url}: ${response.status}`)
|
||||
}
|
||||
|
||||
const { features } = (await response.json()) as { features: Feature[] }
|
||||
|
||||
const regions: { code: string; name: string; rings: Ring[] }[] = []
|
||||
for (const feature of features) {
|
||||
const code = regionCode(feature.properties)
|
||||
if (!code) {
|
||||
console.warn(` skipped "${feature.properties.NAME}": no region code`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (config.exclude.includes(code)) {
|
||||
continue
|
||||
}
|
||||
|
||||
regions.push({
|
||||
code,
|
||||
name: String(feature.properties.NAME_LONG ?? feature.properties.NAME),
|
||||
rings: ringsOf(feature.geometry).map((ring) => ring.map(miller)),
|
||||
})
|
||||
}
|
||||
|
||||
const duplicate = regions.find((region, index) => regions.findIndex((other) => other.code === region.code) !== index)
|
||||
if (duplicate) {
|
||||
throw new Error(`Region code "${duplicate.code}" is used twice`)
|
||||
}
|
||||
|
||||
// Fit the projected shapes into the viewBox. The y axis points down in SVG.
|
||||
let [minX, minY, maxX, maxY] = [Infinity, Infinity, -Infinity, -Infinity]
|
||||
for (const [x, y] of regions.flatMap((region) => region.rings.flat())) {
|
||||
minX = Math.min(minX, x)
|
||||
maxX = Math.max(maxX, x)
|
||||
minY = Math.min(minY, y)
|
||||
maxY = Math.max(maxY, y)
|
||||
}
|
||||
|
||||
const scale = config.width / (maxX - minX)
|
||||
const translate: Position = [-minX * scale, maxY * scale]
|
||||
const height = Math.ceil((maxY - minY) * scale)
|
||||
const round = (value: number): number => Math.round(value * 1000) / 1000
|
||||
|
||||
regions.sort((a, b) => a.code.localeCompare(b.code))
|
||||
|
||||
const data = {
|
||||
width: config.width,
|
||||
height,
|
||||
projection: { type: 'miller', scale: round(scale), translate: translate.map(round) },
|
||||
regions: Object.fromEntries(
|
||||
regions.map((region) => {
|
||||
const path = region.rings.map((ring) => ringPath(ring.map(([x, y]): Position => [x * scale + translate[0], translate[1] - y * scale]))).join('')
|
||||
|
||||
if (!path) {
|
||||
throw new Error(`Region "${region.code}" has no shape left after rounding`)
|
||||
}
|
||||
|
||||
return [region.code, { name: region.name, path }]
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
const source = `// Generated by \`pnpm run generate:maps\` from Natural Earth (public domain,
|
||||
// https://www.naturalearthdata.com). Do not edit by hand.
|
||||
|
||||
import type { VectorMapData } from '../src/vector-map'
|
||||
|
||||
export const ${config.name}: VectorMapData = ${JSON.stringify(data)}
|
||||
`
|
||||
|
||||
const file = join(outDir, `${config.name}.ts`)
|
||||
const options = await prettier.resolveConfig(file)
|
||||
const formatted = await prettier.format(source, { ...options, filepath: file })
|
||||
writeFileSync(file, formatted)
|
||||
|
||||
console.log(`✓ ${config.name}: ${regions.length} regions, ${config.width}x${height}, ${(formatted.length / 1024).toFixed(1)} kB`)
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
for (const config of maps) {
|
||||
await generate(config)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -9,20 +9,26 @@ interface CreateViteConfigOptions {
|
||||
outDir: string
|
||||
banner?: string
|
||||
minify?: boolean | 'esbuild'
|
||||
/** UMD only: add the exports to an existing global of the same name instead of replacing it */
|
||||
extend?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Vite configuration for building libraries
|
||||
*/
|
||||
export function createViteConfig({ entry, name, fileName, formats, outDir, banner, minify = false }: CreateViteConfigOptions): UserConfig {
|
||||
export function createViteConfig({ entry, name, fileName, formats, outDir, banner, minify = false, extend = false }: CreateViteConfigOptions): UserConfig {
|
||||
// Vite 8 (Rolldown) always emits const bindings and no longer accepts the
|
||||
// Rollup-only generatedCode.constBindings option
|
||||
const rollupOutput: { banner?: string } = {}
|
||||
const rollupOutput: { banner?: string; extend?: boolean } = {}
|
||||
|
||||
if (banner) {
|
||||
rollupOutput.banner = banner
|
||||
}
|
||||
|
||||
if (extend) {
|
||||
rollupOutput.extend = true
|
||||
}
|
||||
|
||||
const config: UserConfig = {
|
||||
// Library builds: never copy <root>/public into outDir (see copy-assets.ts).
|
||||
publicDir: false,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
"@tabler/preview": minor
|
||||
"@tabler/docs": minor
|
||||
---
|
||||
|
||||
Added the `VectorMap` plugin in `tabler-vector-map.js`: an SVG world map with values, markers, lines, tooltips and zoom.
|
||||
@@ -8,7 +8,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const baseName = process.env.BASE_NAME || 'tabler'
|
||||
const entryFile = baseName
|
||||
const libraryName = baseName
|
||||
// Two kinds of entry add to a global instead of creating their own, so they
|
||||
// can be loaded next to the main bundle:
|
||||
// - `tabler-vector-map` is an add-on, its UMD build extends the `tabler` global;
|
||||
// - `maps/*` are map files, each adds its map to the `tablerVectorMaps` global.
|
||||
const isMap = baseName.startsWith('maps/')
|
||||
const isAddon = baseName === 'tabler-vector-map'
|
||||
const libraryName = isMap ? 'tablerVectorMaps' : isAddon ? 'tabler' : baseName
|
||||
|
||||
const bannerText = getBanner()
|
||||
|
||||
@@ -26,4 +32,5 @@ export default createViteConfig({
|
||||
outDir: path.resolve(__dirname, '../dist/js'),
|
||||
banner: bannerText,
|
||||
minify: false,
|
||||
extend: isMap || isAddon,
|
||||
})
|
||||
@@ -0,0 +1,422 @@
|
||||
// Generated by `pnpm run generate:maps` from Natural Earth (public domain,
|
||||
// https://www.naturalearthdata.com). Do not edit by hand.
|
||||
|
||||
import type { VectorMapData } from '../src/vector-map'
|
||||
|
||||
export const world: VectorMapData = {
|
||||
width: 1000,
|
||||
height: 489,
|
||||
projection: { type: 'miller', scale: 159.155, translate: [500, 316.038] },
|
||||
regions: {
|
||||
AE: { name: 'United Arab Emirates', path: 'M643.3 247.4l.5-.2 .1 .8 2.1-.4 2.3 0 1.7 .1 1.9-2 2.1-1.9 1.8-1.8 .5 1 .4 2.4-1.5 0-.2 1.9 .5 .4-1.3 .6 0 1.2-.8 1.2 0 1.2-.6 .6-8.4-1.5-1-3z' },
|
||||
AF: {
|
||||
name: 'Afghanistan',
|
||||
path: 'M684.8 207.2l1.5 0 2.1 .7 .9 .4 2-1 .9 .6 .9-1.5 1.7 .1 .4-.5 .3-1.3 1.2-1.1 1.5 .7-.3 1 .8 .2-.2 2.6 1.1 1.1 .9-.7 1.3-.3 1.7-1.4 1.9 .2 2.9 0 .5 .9-1.6 .4-1.5 .6-3.1 .4-3 .6-1.6 1.4 .6 1.3 .3 1.6-1.4 1.3 .2 1.2-.8 1.2-2.6-.1 1 2-1.7 .8-1.2 1.9 .1 1.8-1 .9-1.1-.3-2.1 .4-.3 .9-2.1 0-1.5 1.7-.1 2.6-3.6 1.3-1.9-.3-.6 .7-1.7-.4-2.8 .4-4.6-1.5 2.5-2.8-.2-2-2.1-.5-.2-1.9-.9-2.5 1.1-1.7-1.2-.5 .8-2.2 1.1-4 2.9 1.2 2.1-.4 .5-1.4 2.2-.5 1.6-1 .6-2.5 2.3-.6 .4-1.2 1.3 .9z',
|
||||
},
|
||||
AL: { name: 'Albania', path: 'M558.4 195.9l-.1 .9-.9 .4-.1 1.1-1.3 1.6-.5-.2-.1-.8-1.5-1.1-.2-1.5 .2-2.3 .4-1-.5-.5-.2-1.1 1.2-1.7 .2 .7 .8-.3 .5 .9 .7 .3 .2 1.2-.4 1.2 .4 1.4z' },
|
||||
AM: { name: 'Armenia', path: 'M629.2 202.7l-1 .1-1.2-1.9 .1-.5-1.3 0-.8-.9-.6 .1-1.1-1-2-.8 .2-1.6-.4-1.1 3.8-.6 .6 .9 1.1 .6-.6 .8 1.5 1.1-.8 1.1 1.2 .9 1.2 .5z' },
|
||||
AO: {
|
||||
name: 'Angola',
|
||||
path: 'M536.1 329.3l-1 .6-.5 .7-.1 1.2-.7 .3-.7-2.1 1.1-1.2 .9-.4zM534.2 333l1.2-.4 .8 .1 1-.4 8.2 .1 .6 2.1 .8 1.6 .7 .9 1 1.5 1.9-.2 .9-.4 1.5 .4 .4-.7 .7-1.7 1.8-.1 .1-.4 1.4-.1-.2 1.1 3.4-.1 0 1.8 .6 1.1-.4 1.6 .2 1.8 .9 1-.2 3.4 .7-.3 1.2 .1 1.8-.5 1.2 .2 .3 .9-.3 1.3 .5 1.4-.4 1 .2 1-5.8 0-.1 9 1.9 2.3 1.8 1.8-5.1 1.2-6.7-.4-2-1.4-11.2 .1-.4 .2-1.7-1.3-1.8-.1-1.7 .5-1.3 .6-.3-1.8 .4-2.5 1-2.6 .1-1.2 .9-2.6 .7-1.2 1.6-1.8 .9-1.3 .3-2-.2-1.6-.8-1-.8-1.7-.6-1.7 .1-.6 .9-1.1-.9-2.7-.5-1.9-1.4-1.8z',
|
||||
},
|
||||
AR: {
|
||||
name: 'Argentina',
|
||||
path: 'M309.3 477.5l1.1 1.8 1.4 2.8 3.6 2.3 3.9 1-1.2 1.9-2.7 .2-1.4-1.4-1.7-.1-2.9 0zM339.9 402.6l-.7 2.4-.7 3.2 0 3.1-.6 .6-.2 2.1-.2 1.6 3.5 2.7-.3 2.2 1.7 1.3-.1 1.6-2.7 4.1-4.1 1.8-5.6 .6-3.1-.3 .6 1.9-.5 2.5 .5 1.6-1.7 1.2-2.8 .5-2.7-1.2-1.1 .8 .4 3.3 1.9 1 1.5-1 .8 1.7-2.5 1.1-2.3 2.1-.4 3.4-.6 1.8-2.6 0-2.2 1.8-.8 2.6 2.7 2.5 2.7 .7-1 3.2-3.3 2-1.8 4.2-2.5 1.5-1.2 1.7 .9 3.8 1.9 2.2-1.2-.2-2.6-.6-6.7-.5-1.1-2.2 0-2.7-1.8 .2-1-1.3-.2-3.8 2.1-1.6 .9-2.3-.3-1.8 1.4-3 1-4.6-.3-2 1.3-.7-.3-1.2-1.3-.7 .9-1.5-1.3-1.2-.6-3.9 1.1-.7-.5-4 .7-3.4 .7-2.9 1.7-1.2-.9-3.1 0-3 2.1-2 0-2.7 1.6-3.1 0-2.8-.8-.6-1.2-5.3 1.7-3.2-.3-2.9 1-2.7 1.8-2.9 2-1.8-.8-1.2 .5-.9 0-5 3-1.4 .9-3.1-.3-.7 2.3-2.7 3.6 .8 1.7 2.1 1.1-2.4 3.1 .1 .5 .7 5.1 4.7 2.3 .5 3.3 2.2 2.9 1.1 .4 1.3-2.7 4.6 2.8 .8 3.1 .4 2.2-.5 2.5-2.2 .5-2.7 1.3-.5 1.4 1.7 0 2.4-2.4 1.6-1.8 1.2-3.2 2.9z',
|
||||
},
|
||||
AT: { name: 'Austria', path: 'M547.2 171l-.2 1.4-1.6 0 .5 .8-.9 2.3-.5 .6-2.5 .1-1.4 .7-2.2-.2-4-.9-.6-1.2-2.8 .6-.3 .6-1.7-.5-1.4-.1-1.3-.6 .5-.9-.1-.6 .8-.2 1.4 1 .4-.9 2.4 .1 2-.6 1.4 .1 .8 .7 .3-.6-.4-2.3 1-.4 1-1.7 2 1.2 1.6-1.5 1-.3 2.1 1.1 1.3-.2 1.3 .7-.2 .5z' },
|
||||
AU: {
|
||||
name: 'Australia',
|
||||
path: 'M910.2 436.1l1.7 .2 .2 3.9-.9 1.2-.3 2.7-1-.9-1.9 2.3-.6-.2-1.7-.1-1.7-2.8-.4-2.3-1.6-2.9 .1-1.5 1.8 .3 2.7 1.2 1.5-.5zM850.4 408.7l-2.9 1.6-2.4 .7-.6 1.6-1 1.3-2.4 .1-1.7 .3-2.5-.6-2 .3-1.9 .2-1.6 1.6-.8-.1-1.4 .9-1.4 1-2-.1-1.8 0-3-2-1.5-.6 .1-1.8 1.4-.5 .4-.7-.1-1.1 .4-2.1-.3-1.9-1.5-3-.5-1.8 .2-1.7-1.2-2 0-.9-1.3-1.2-.3-2.3-1.6-2.4-.4-1.3 1.3 1.3-1-2.7 1.4 .8 .8 1.2 0-1.5-1.4-2.4-.3-.9-.6-.9 .3-1.7 .6-.7 .3-1.5-.3-1.7 1.2-2.1 .2 2.2 1.2-2 2.2-.9 1.4-1.3 2.1-1 1.3-.3 .7 .4 2.2-1.1 1.7-.3 .4-.6 .8-.3 1.5 .1 2.9-.9 1.5-1.3 .7-1.5 1.7-1.5 .1-1.1 .1-1.6 1.9-2.4 1.2 2.5 1.2-.6-1-1.3 .8-1.4 1.3 .6 .3-2.2 1.5-1.4 .7-1.1 1.4-.5 0-.8 1.2 .4 .1-.8 1.2-.4 1.4-.3 2 1.3 1.6 1.6 1.7 .1 1.8 .2-.6-1.5 1.3-2.3 1.3-.7-.5-.7 1.2-1.7 1.7-1 1.4 .4 2.4-.5-.1-1.5-2-.9 1.5-.4 1.8 .7 1.5 1.1 2.3 .7 .8-.2 1.7 .8 1.7-.8 1 .3 .6-.6 1.3 1.4-.7 1.5-1.1 1.2-.9 .1 .3 1.1-.8 1.4-1 1.4 .2 .8 2.2 1.6 2.1 .9 1.5 1 2 1.7 .8 0 1.4 .7 .4 .9 2.7 .9 1.8-.9 .6-1.6 .5-1.2 .4-1.6 .8-2.3-.4-1.3 .2-.9-.3-1.6 .4-2.1 .5-.6-.4-.9 .6-1.5 .6-1.6 0-.8 1.1-1 .8 1.4 .2 1.7 .6 .4 .2 1.1 1 1.5 .2 1.6-.1 1 1 2.2 1.8-1 .9 1.2 1.3 1.1-.3 1.2 .6 2.5 .4 1.4 .7 .3 .8 2.5-.3 1.5 .9 1.9 3 1.5 2 1.4 1.9 1.3-.4 .7 1.6 1.8 1.1 3.1 1.1-.6 1.1 1.3 .7-.5 .5 3.1 1.9 1.8 1.3 1.1 2.2 2.4 .8 2.4 0 1.7-.1 1.9 1.3 2.5-.2 2.7-.5 1.4-.7 2.7 0 1.7-.5 2.2-1.2 2.8-2.1 1.6-1 2.4-.9 1.5-.8 2.7-1.1 1.6-.7 2.4-.4 2.2 .2 1-1.6 1.1-3.1 .1-2.6 1.3-1.3 1.3-1.7 1.4-2.3-1.5-1.7-.5 .5-1.7-1.6 .6-2.4 2.3-2.4-.8-1.6-.5-1.5-.3-2.7-.9-1.8-2-.5-2.4-.7-1.6-1.4-1.3-2.6-.4 .9-1.5-.7-2.4-1.3 2.2-2.5 .6 1.4-1.7 .5-1.8 1-1.6-.2-2.3-2.3 2.7-1.7 1-1.1 2.5-2.1-1.3 .1-1.6-1.8-2.3-1.4-1.1 .5-.8-3.6-1.8-1.9-.1-2.7-1.5-5 .3-3.6 1.1-3.1 1z',
|
||||
},
|
||||
AZ: {
|
||||
name: 'Azerbaijan',
|
||||
path: 'M628.9 192.5l.8 .1 1.9 2 1.2 .3 .5-.9 1.7-1.3 1.4 1.7 1.4 2.4 1.3 .1 .9 .9-2.3 .3-.5 2.5-.5 1.2-1 .7 .1 1.6-.7 .2-1.7-1.7 .9-1.6-.8-1-1 .3-3.3 2.4-.1-2.3-1.2-.5-1.2-.9 .8-1.1-1.5-1.1 .6-.8-1.1-.6-.6-.9 .7-.5 2.1 1 1.5 .2 .3-.4-1.3-1.8zM628.2 202.8l-1.9-.5-1.4-1.5-.5-1.2 .6-.1 .8 .9 1.3 0-.1 .5z',
|
||||
},
|
||||
BA: { name: 'Bosnia and Herzegovina', path: 'M551.6 189.9l-2.5-1.3-1.1-1.4-1-.8-1.3-1.2-.6-1.1-1.3-1.6 .5-1.4 1 .8 .6-.7 1.3-.1 2.4 .6 1.9-.1 1.3 .8 1 0-.7 1.5 1.3 1.3-.4 1.6-.6 .1-.5 .3-.9 .8z' },
|
||||
BD: { name: 'Bangladesh', path: 'M757.4 253.8l0 2.1-1-.4 .2 2.3-.8-1.5-.2-1.5-.5-1.4-1.2-1.7-2.5-.1 .2 1.2-.8 1.6-1.2-.6-.4 .6-.8-.4-1.1-.2-.4-2.4-1-2.2 .5-1.8-1.7-.8 .6-1.1 1.7-1.1-2-1.5 1-2.1 2.2 1.3 1.3 .2 .3 2 2.6 .4 2.6 0 1.6 .5-1.3 2.5-1.2 .2-.9 1.6 1.5 1.6 .5-1.9 .8 0z' },
|
||||
BE: { name: 'Belgium', path: 'M517.1 161.3l-.3 2.5-.7 .1-.3 2.1-2.5-1.7-1.4 .3-1.9-1.7-1.3-1.5-1.3 0-.4-1.3 2.2-.8 2 .3 2.6-.7 1.8 1.6z' },
|
||||
BF: { name: 'Burkina Faso', path: 'M485 287.1l-.2-1.6 .8-1.2-.1-.9 2.2-2.4 .4-1.9 .8-.7 1.3 .4 1.2-.6 .4-.7 2.1-1.3 .5-.9 2.6-1.2 1.6-.4 .7 .6 1.7 0-.2 1.3 .4 1.3 1.6 1.9 0 1.3 3.2 .7 0 1.9-.6 .9-1.4 .2-.5 1.3-1 .3-2.4-.1-1.3-.2-.9 .4-1.2-.2-4.9 .2 0 1.6 .3 2.1-1.9-.8-1.3 .2-.9 .7-1.3-.6-.5-1z' },
|
||||
BG: { name: 'Bulgaria', path: 'M562.9 184.5l.8 1.4 1.1-.2 2.1 .5 4.1 .2 1.4-.9 3.3-.8 2 1.2 1.6 .4-1.4 1.4-1 2.4 .9 1.9-2.4-.4-2.9 1 0 1.7-2.5 .3-2-1.2-2.2 .9-2-.1-.2-2.2-1.4-1 .4-.5-.3-.4 .5-1.1 1.1-1-1.4-1.5-.2-1.2z' },
|
||||
BI: { name: 'Burundi', path: 'M584.6 322.7l.2 1.1 .6 .7 0 .9-.7 .6-1 1.4-1.1 1-1.1 .1-.2-3.3-.7-1.3 1.7 .2 .9-1.5z' },
|
||||
BJ: { name: 'Benin', path: 'M507.5 298.6l-2.3 .4-.7-2 .1-6.4-.5-.6-.1-1.3-1-1-.9-.9 .4-1.4 1-.3 .5-1.3 1.4-.2 .6-.9 .9-.8 1 0 2.1 1.6-.1 .9 .6 1.7-.5 1.1 .3 .8-1.4 1.7-.8 .9-.5 1.8 0 1.7z' },
|
||||
BN: { name: 'Brunei Darussalam', path: 'M820.7 300.9l-.1 1.4-.2 1.7-1.3 0-.6 .9-1.3-1.4 1.1-1.1z' },
|
||||
BO: {
|
||||
name: 'Bolivia',
|
||||
path: 'M306.9 346.6l2 .2 1.5 0 .6-.9 2.4-1.1 1.5-1.1 3.6-.5-.3 2.1 .4 1.1-.3 1.9 3 2.5 3.2 .5 1 1.1 1.9 .5 1.2 .8 1.7 0 1.6 .8 .2 1.7 .5 .8 0 1.2-.8 .1 1.1 3.3 5.3 .1-.4 1.7 .3 1.1 1.5 .8 .7 1.8-.5 2.2-.8 1.3 .3 1.6-.9 .6 0-.8-2.6-1.5-2.6-.1-4.8 .9-1.4 2.5 0 1.6-1.1 3.5-.5-.7-3.1-.1-1.1 2.4-1.7-2.1-3.6-.8-2.3 2.7-2 .4-1.1-4-1.5-3.3 .9-2.8-1.5-1.2-.3-2.1-1.4-1.9 1.7-3.1-1.1-2.4 .6-1-.5-1 1.1-1.4 0-2.4 .2-2 .6-1z',
|
||||
},
|
||||
BR: {
|
||||
name: 'Brazil',
|
||||
path: 'M351.7 413.5l-.7-1.7 1.2-1.5-1.6-2.1-2.2-1.7-2.8-2-1.1 .1-2.8-2.3-1.8 .3 3.7-4.2 3.2-2.9 1.8-1.2 2.4-1.6 0-2.4-1.4-1.7-1.3 .5 .5-1.7 .4-1.7 0-1.6-1-.6-1.1 .5-1-.1-.3-1.2-.3-2.7-.5-.8-1.9-.8-1.1 .6-2.9-.6 .1-3.9-.8-1.7 .9-.6-.3-1.6 .8-1.3 .5-2.2-.7-1.8-1.5-.8-.3-1.1 .4-1.7-5.3-.1-1.1-3.3 .8-.1 0-1.2-.5-.8-.2-1.7-1.6-.8-1.7 0-1.2-.8-1.9-.5-1-1.1-3.2-.5-3-2.5 .3-1.9-.4-1.1 .3-2.1-3.6 .5-1.5 1.1-2.4 1.1-.6 .9-1.5 0-2-.2-1.6 .5-1.3-.4 .2-4.2-2.3 1.6-2.4 0-1.1-1.5-1.8-.2 .6-1.2-1.6-1.7-1.1-2.5 .7-.5 0-1.2 1.7-.8-.3-1.5 .7-1 .2-1.3 3.2-1.9 2.3-.5 .3-.4 2.6 .1 1.2-7.6 .1-1.2-.5-1.6-1.2-1 0-2.1 1.6-.4 .5 .3 .1-1.1-1.6-.3 0-1.7 5.4 0 .9-.9 .8 .9 .5 1.6 .5-.3 1.6 1.4 2.1-.2 .6-.8 2-.7 1.2-.4 .3-1.2 2-.8-.2-.6-2.3-.2-.4-1.7 .1-1.9-1.2-.7 .5-.3 2 .4 2.2 .7 .8-.7 2-.4 3.1-1.1 1.1-1-.4-.8 1.4-.1 .7 .6-.4 1.2 1 .4 .6 1.3-.8 1-.4 2.4 .7 1.4 .2 1.3 1.7 1.3 1.4 .1 .3-.5 .9-.1 1.2-.5 .9-.8 1.6 .3 .6-.1 1.6 .2 .2-.6-.5-.5 .3-.8 1.1 .2 1.4-.3 1.5 .6 1.3 .6 .8-.8 .6 .2 .4 .7 1.3-.2 1.1-1 .9-2.1 1.6-2.5 1-.1 .6 1.5 1.6 4.9 1.5 .4 .1 1.9-2.1 2.3 .8 .9 4.9 .4 .1 2.8 2.2-1.8 3.4 1 4.7 1.6 1.3 1.7-.4 1.5 3.2-.8 5.4 1.4 4.1-.1 4.2 2.3 3.5 3.1 2.1 .8 2.4 .2 1 .8 1 3.6 .4 1.7-1.1 4.6-1.4 1.8-3.9 3.9-1.8 3.2-2 2.5-.7 0-.8 2.1 .2 5.3-.8 4.4-.3 1.9-.9 1.1-.4 3.9-2.9 3.7-.4 3.1-2.3 1.2-.6 1.8-3.1 0-4.3 1.1-2 1.3-3.1 .9-3.3 2.3-2.3 2.9-.4 2.3 .4 1.6-.5 3-.6 1.5-1.9 1.7-3.1 5.3-2.5 2.5-1.9 1.4-1.2 2.9z',
|
||||
},
|
||||
BS: { name: 'Bahamas', path: 'M280.6 239.8l1.3-.2 1.9 .1 0 .7-3 .5zM283.9 239.1l2.2 1.3-.5 2.1-.5-.3 .1-1.6-1.3-1.2zM282.8 244.5l.8 .1 1 2.5 0 1.7-.7 .1-.7-1.7-1-.8z' },
|
||||
BT: { name: 'Bhutan', path: 'M754.7 236.9l1.1 .9-.2 1.9-2.2 .1-2.4-.2-1.7 .4-2.5-1.1-.1-.6 1.8-2.2 1.5-.8 2 .7 1.5 .1z' },
|
||||
BW: { name: 'Botswana', path: 'M581.8 378.4l-4 2.2-2.5 2.1-.9 2-.8 1.1-1.5 .2-.5 1.5-.3 .9-1.8 .7-2.2-.2-1.4-.8-1.1-.4-1.4 .7-.7 1.4-1.3 .9-1.4 1.4-2 .3-.6-1.1 .3-1.8-1.7-2.8-.7-.4 0-8.6 2.7-.1 .1-10.3 2.1-.1 4.2-1 1.1 1.2 1.8-1.1 .8-.1 1.6-.6 .5 .2 1 2.3 .6 .5 .9 1.7 3.1 3.2 1.2 .3 0 1 .8 1.8 2.2 .5z' },
|
||||
BY: {
|
||||
name: 'Belarus',
|
||||
path: 'M578.3 141l2.9 1 .4 1 1.4-.5 2.8 .9 .2 1.8-.6 1.1 1.8 2.5 1.1 .7-.2 .7 1.9 .7 .8 1-1.1 .8-2.2-.1-.5 .3 .6 1.3 .7 2.3-2.4 .3-.8 .8-.2 1.8-1.1-.3-2.5 .2-.8-.9-1 .6-1.1-.5-2.1-.1-3.1-.9-2.8-.2-2.2 0-1.5 1-1.3 .2-.1-1.7-.9-1.7 1.7-.8 0-1.5-.7-1.4-.2-1.7 2.7 .1 3-1.5 .7-2.1 2.3-1.3-.3-1.7 1.7-.7z',
|
||||
},
|
||||
BZ: { name: 'Belize', path: 'M252.4 266.1l0-.5 .3-.1 .5 .3 1-1.7 .5 0 0 .4 .6 0-.1 .8-.4 1.2 .2 .5-.3 1 .2 .3-.3 1.4-.6 .8-.5 .1-.5 .9-.9 0 .3-3.2z' },
|
||||
CA: {
|
||||
name: 'Canada',
|
||||
path: 'M158.8 167.9l-.4 0-5.4-3.6-2-1.6-5-1.5-1.5-3.2 .4-2.3-3.6-1.6-.5-3-3.3-2.8-.1-2 1.5-1.8 0-2.5-4.8-2.5-2.8-4.5-1.7-2.9-2.6-1.9-1.8-1.6-1.5-2.2-2.8 1.4-2.7 2.3-2.5-2.7-1.9-1.8-2.7-1.2-2.8-.1 .1-24.9 0-17.7 5.2 1.2 4.3 2.3 2.9 .5 2.5-2 3.3-1.6 4.2 .6 4.1-2.1 4.6-1.3 1.9 2.1 2-1.2 .7-2.3 1.9 .5 4.7 4.4 3.7-3.3 .4 3.7 3.4-.8 1-1.4 3.4 .3 4.2 2.1 6.5 1.7 3.8 .9 2.8-.3 3.7 2.4-3.9 2.4 5 1 7.5-.6 2.4-.8 3 2.9 3-2.4-2.9-2.1 1.8-1.6 3.4-.2 2.2-.5 2.3 1.1 2.8 2.6 3.1-.4 4.9 2.2 4.3-.8 4 .2-.3-3 2.5-.9 4.3 1.7 0 4.5 1.8-3.8 2.2 .1 1.2-4.8-2.9-3.1-3.3-2 .2-5.5 3.3-3.8 3.7 .9 2.8 2.2 3.8 5.7-2.5 2.5 5.2 1 0 4.9 3.7-3.8 3.3 3.2-.8 3.5 2.7 3.2 2.9-3.4 2-4.2 .1-5.3 4 .3 4.1 .8 3.7 2.4 .2 2.4-2.1 2.6 2 2.5-.4 2.3-5.4 3.3-3.9 .7-2.8-1.4-.9 2.3-2.7 3.9-.8 1.9-3.2 3.1-4 .3-2.2 1.8-.1 2.9-3.3 .5-3.4 3.5-3 4.8-1.1 3.3-.1 4.8 4.1 .7 1.2 3.8 1.3 3 3.9-.8 5.2 1.7 2.7 1.5 2 1.9 3.5 1.1 3 1.6 4.5 .2 3.1 .4-.5 3.3 .9 3.8 2 4.2 4.1 3.5 2.2-1.2 1.5-3.8-1.5-5.9-1.9-2.1 4.4-1.8 3.1-2.7 1.6-2.7-.2-2.7-1.9-3.4-3.4-3 3.3-4.3-1.2-3.8-1-6.7 2-.9 4.7 1.1 2.9 .4 2.3-1.1 2.6 1.5 3.4 2.4 .8 1.7 5 .3-.1 3.5 .9 5.3 2.6 .6 2 2.4 4-2.3 2.7-4.5 1.8-1.9 2.2 3.7 3.6 5.2 3.1 4.8-1.2 2.4 3.7 2.3 2.5 2.2 4.5 1 1.7 1.2 1.1 3.2 2.2 .5 1.1 1.5 .2 4.2-2 1.4-2 1.3-4.6 1.3-3.5 3-4.7 .6-5.9-.8-4.2 0-2.8 .2-2.4 2.6-3.5 1.6-4 4.7-3.2 3.3 2.3-.6 4.5-4.6 5.8-3 4.2-.4 2.4 1.8-2.6 2.4 .9 3.8 .9 2.6 3.6 1.7 4.6-.5 2.8-3.9 .2 2.6 1.8 1.2-3.5 2.3-6.1 2-2.8 1.4-3.1 2.5-2.1-.3-.1-2.9 4.8-2.8-4.4 .1-3.1 .4-1.8-1.9 0-4.8-1.2-1-1.9 .6-.9-.9-2.1 2.6-.9 2.7-1 1.6-1.2 .5-.8 .2-.3 .9-5.1 0-4.3 0-1.2 .6-3 2.5-.3 .2-.9 1.4-2.5 0-2.8 0-1.2 .5 .4 .7 .3 1-.1 .4-3.6 1.6-2.9 .5-3.2 1.8-.7 0-.9-.5-.4-.5 .1-.3 .6-1.2 1.3-1.8 .8-2-.5-3-.6-3.1-2.9-1.6 .3-.6-.4-.5-.7 0-.6-.5-.1-.8-.6 .3-.7-.1 .2-.3-.7-.4-.3-.9-2.1-1.1-2.3-1.2-2.7-1.4-2.6-1.2-2.5 1-.9 0-3.4-.9-2.3 .4-2.6-1.1-2.9-.5-1.9-.3-.9-.6-.5-1.9-.9 0 0 1.4-5.8 0-9.5 0-9.4 0-8.4 0-8.3 0-8.2 0-8.5 0-2.7 0-8.2 0zM266.7 115.2l2-2 3.9 0-.1 .9-3.2 2.3-2-.1zM278.4 65.9l-3.1-2.8 .2-1.9 1.3-.4 6.4 .6 4.7 2.9 .3 1.5-3-.1-3-.2-3 .8zM276.9 116.8l1.1-1.3 1.1 .1 .7 .8-1.1 2.3-1.2-.4-.7-1.2zM240 54.1l-1.5 2.2-4.1-.4-3.3-1.5 1.4-2.5 4-1.5 2.5 2zM239.3 39.6l-1.2 .2-5.2-.4-.8-1.7 5.6 .1 2 1.1zM231.2 32.1l3.4 2.1-.8 2.2-4.1 1.2-2.3-1.4-1.2-2.2-.2-2.6 3.6 .3zM255.1 57.4l-4.4-.7-7.4-1.8-1-3-.3-2.8-2.8-2.5-5.8-.7-3.2-1.8 1.1-2.4 5.7 .4 3.1 1.9 5.4-.1 2.4 1.9-.6 2.2 3.2 1.3 1.8 1.3 3.7 .3 4.1 .4 4.4-1.2 5.6-.5 4.5 .4 3 2.2 .6 2.3-1.7 1.4-4.1 1.2-3.6-.7-8 .9zM190.9 35.8l3.9 1-.9 1.7-5.2 1.7-4.1-1.9 2.3-1.9zM191.8 31.9l3.6 1.2-3.4 1.2-4.6 0 0-.9 2.9-1.8zM345.6 159.4l-1.5 2.4-1.9 3.1 1.8-1.2 1.9 .8-1 1.3 2.5 .9 1.3-.8 2.8 1.1-.9 2.6 1.9-.6 .4 1.9 .9 2.2-1.2 3.1-1.3 .1-1.8-.7 .6-2.8-.8-.5-3.2 3.1-1.6-.2 1.9-1.6-2.7-.9-2.9 .2-5.4-.1-.5-1 1.8-1.3-1.2-.9 2.3-2.2 2.9-5.8 1.7-2 2.4-1.3 1.3 .1zM267 103.4l3 1.6 3.2 1.4 .3 2.1 2-.4 2 1.5-2.5 1.4-4.3-1.1-1.6-1.9-2.7 2.3-4 2.3-.9-2.6-3.8 .4 2.4-2.2 .4-3.5 .9-4.1 2 .3 .6 2 1.4-.7zM281.2 68.3l2.6-2.1 6.2 2.7 3.8 2.4 .4 2.3 5.1-1.2 2.9 3.3 6.7 2 2.4 2 2.7 4.6-5.1 2.3 6.5 3.1 4.4 1 4 4.4 4.4 .3-.9 3.2-4.9 5.3-3.4-1.9-4.3-4.4-3.6 .6-.4 2.6 2.9 2.6 3.8 2.1 1.2 1.2 1.8 4.4-1 3.1-3.5-1.2-7-3.5 4 3.8 2.9 2.6 .4 1.5-7.5-1.7-6-2.5-3.4-2.1 1-1.3-4.1-2.3-4.1-2.1 .1 1.3-8.1 .7-2.3-1.5 1.8-3.4 5.2 0 5.8-.6-1-1.7 1-2.3 3.6-4.5-.8-2.1-1-1.6-4.3-2.3-5.6-1.7 1.8-1.2-3-3.1-2.4-.3-2.2-1.7-1.5 1.5-5 .7-10.2-1.2-5.8-1.4-4.5-.8-2.3-1.7 2.9-2.3-4-.1-.9-5.2 2.2-4.7 2.8-2.2 7.2-1.4-2.1 3.5 2.2 3.2 2.6-4.2 7-2.2 4.8 5.5-.4 3.4zM237.5 58.8l5.8 .2 5.3 1.3-4.2 4.8-3.3 1-3 3.9-3.1-.2-1.8-4.6 .1-2.6 1.4-2.3zM158.7 47.7l4.8-4.3 5.7-3.8 4.2 .1 3.8-.9-.4 4.6-2.1 2-2.6 .3-5.2 2.4-4.4 .9zM131.4 149.2l2.6-.3-.8 4.3 2.4 3.1-1.1-.1-1.7-1.7-1-1.7-1.4-1.2-.5-1.6 .2-1.2zM207 28.8l5.4 .8 7.5 2.3 2.2 2.9 1 2.5-4.5-.7-4.6-1.9-6.2-.3 2.7-1.8-3.3-1.4zM156.9 169.6l-1.4 .5-4.5-1.6-.9-1.3-2.5-1.2-.5-1.1-2.8-.6-1.1-2 .2-.8 3 .8 1.7 .5 2.6 .4 .9 1.2 1.4 1.8 2.8 1.4zM162.4 57.1l4 1.1 7.1 .3 2.7 1.6 2.9 2.3-3.5 1.3-6.8 3.7-3.4 3.7 0 2.2-7.3 2.5-1.5-2.3-6.4-2.7 1.2-2.2 1.9-3.8 2.4-3.5-2.7-3.4zM200.5 49.3l2.5-1 2.9 .3 .5 2.8-1.7 2.6-9.4 .9-7 2.4-4.3 .1-.3-1.8 5.8-2.5-12.6 .7-3.9-1 3.8-5.5 2.6-1.6 7.8 1.9 5 3.4 4.8 .4-3.9-5.4 2.5-2.2 2.9 .7 .9 2.8zM204.1 64.5l3.1 2.1 1.8 5.1 .8 3.6 4.7 2.5 5 2.4-.3 2.1-4.6 .4 1.8 1.9-.9 1.8-5.1-.8-4.7-1.3-3.3 .3-5.2 1.7-7 .7-5 .4-1.5-2.2-3.8-1.4-2.4 .6-3.4-3.9 1.8-.6 4.3-.8 3.9 .2 3.6-.8-5.3-1.2-6 .4-3.9-.1-1.5-1.9 6.5-2-4.3 .1-4.9-1.4 2.4-3.9 1.9-2.1 7.4-3.2 2.9 1-1.4 2.5 6.2-1.6 3.8 2.7 3.2-2.7 2.5 1.7 2.3 5.1 1.4-2.1-2-5.4 2.4-.8zM221 66.4l-3.1-3.4 3.3-2.6 3.3 1.1 5-.7 .7 1.6-2.6 2.5 4.2 2.3-.5 4.7-4.5 2-2.7-.5-1.9-1.9-6.9-4 0-1.7zM203.9 61.7l3.7-.2 2.1 1.2-2.4 3.5-4.4-3.8zM226.4 44.3l2.1 2.6 .1 3-1.3 4.1-4.5 .6-3-.9 0-3.2-4.5 .4-.2-4.4 3 .2 4.2-2 3.9 .3zM233.3 20.6l1.9-2 2.9-.5-1.3-1.5 6.5-.3 3.6 3.5 4.6 1.4 4.6 1.2 2.2 4.1 3.3 2.1-3.8 1.8-5.1 4.6-4.9 .4-5.8-.7-3-2.5 .1-2.2 2.2-1.7-5.1 0-3.1-2-1.7-2.9zM245.6 12.1l4.1-1.3 3.3-.2 5.4-1.1 4.1-2.6 3.4 .4 3 1.9 2.2-3.7 3.6-1.1 5-.8 8.5-.3 1.5 .8 8-1.2 6 .4 6 .5 7.4 .5 6 .9 5.1 1.9-.1 1.8Line truncated
|
||||
},
|
||||
CD: {
|
||||
name: 'Democratic Republic of the Congo',
|
||||
path: 'M581.5 328.5l.5 2.6-.3 1.5 .6 1.6 1.6 1.5 1.5 3.6-1.1-.3-3.7 .4-.8 .4-.8 1.8 .6 1.2-.4 3.3-.4 2.8 .8 .5 1.9 1.1 .8-.5 .2 3.1-2.1 0-1.2-1.6-1-1.2-2.1-.4-.6-1.5-1.7 .9-2.3-.4-.9-1.3-1.8-.2-1.3 .1-.1-.9-1-.1-1.2-.2-1.8 .5-1.2-.1-.7 .3 .2-3.4-.9-1-.2-1.8 .4-1.6-.6-1.1 0-1.8-3.4 .1 .2-1.1-1.4 .1-.1 .4-1.8 .1-.7 1.7-.4 .7-1.5-.4-.9 .4-1.9 .2-1-1.5-.7-.9-.8-1.6-.6-2.1-8.2-.1-1 .4-.8-.1-1.2 .4-.4-.9 .7-.3 .1-1.2 .5-.7 1-.6 .7 .3 1-1.1 1.5 .1 .2 .8 1 .5 1.6-1.8 1.7-1.3 .7-.9-.1-2.3 1.2-2.7 1.2-1.5 1.9-1.3 .3-.9 .1-1 .4-1-.1-1.5 .3-2.5 .6-1.7 .8-1.5 .2-1.7 .2-1.9 1.1-1.5 1.5-.8 2.3 .9 1.7 1 2.1 .3 2 .5 .9-1.6 .3-.3 1.3 .3 3.1-1.4 1.1 .6 .9-.1 .4-.6 1.1-.3 2 .3 1.8 .1 .9-.3 1.7 2.3 1.3 .3 .7-.4 1.3 .1 1.5-.6 .7 1.2 2.4 1.9-.1 3.2 1.1 .4-.9 1-1.1 .7-1 1.5-.6 1.3-.2 2.2-.6 1.1 0 2.1-.8 .7-.1 1.7-.4 .2-.3 1.5 .7 1.3z',
|
||||
},
|
||||
CF: {
|
||||
name: 'Central African Republic',
|
||||
path: 'M576 301.5l-.9 .3-1.8-.1-2-.3-1.1 .3-.4 .6-.9 .1-1.1-.6-3.1 1.4-1.3-.3-.3 .3-.9 1.6-2-.5-2.1-.3-1.7-1-2.3-.9-1.5 .8-1.1 1.5-.2 1.9-1.8-.2-1.9-.4-1.7 1.5-1.4 2.5-.3-.8-.1-1.2-1.3-.9-1-1.5-.3-1-1.3-1.4 .2-.8-.2-1.2 .2-2.2 .6-.5 1.4-2.8 2.3-.2 .6-.7 .4 0 .7 .6 3.5-1 1.2-1.1 1.4-1-.2-1 .7-.2 2.7 .1 2.6-1.3 2-3 1.5-1.2 1.7-.4 .3 1.2 1.6 1.7 0 1.2-.4 1.1 .2 .9 .9 .8 2.1 1.2 1.6 1.2 0 .9 1.9 1.4 1.1 1.2 .7 1.7 2.1 1.1z',
|
||||
},
|
||||
CG: {
|
||||
name: 'Republic of the Congo',
|
||||
path: 'M551.3 306.3l-.2 1.7-.8 1.5-.6 1.7-.3 2.5 .1 1.5-.4 1-.1 1-.3 .9-1.9 1.3-1.2 1.5-1.2 2.7 .1 2.3-.7 .9-1.7 1.3-1.6 1.8-1-.5-.2-.8-1.5-.1-1 1.1-.7-.3-1-.9-.9 .4-1.1 1.2-2.3-2.9 2.1-1.5-1-1.9 .9-.7 1.9-.3 .2-1.2 1.5 1.3 2.5 .1 .8-1.3 .4-1.9-.3-2.1-1.3-1.7 1.2-3.2-.7-.5-2.1 .2-.8-1.4 .2-1.3 3.5 .1 2.3 .8 2.2 .6 .2-1.5 1.4-2.5 1.7-1.5 1.9 .4z',
|
||||
},
|
||||
CH: { name: 'Switzerland', path: 'M526.7 173.1l.1 .6-.5 .9 1.3 .6 1.4 .1-.2 1.5-1.2 .6-2.1-.5-.6 1.4-1.3 .1-.5-.5-1.6 1.2-1.3 .1-1.2-.7-.9-1.5-1.4 .5 .1-1.6 2-2-.1-.8 1.3 .3 .7-.6 2.4 0 .6-.8z' },
|
||||
CI: {
|
||||
name: "Côte d'Ivoire",
|
||||
path: 'M477.7 287.6l.4-.3 .7 .5 2.2 0 .5-.8 .5 0 .8-.3 .4 1.2 .6-.4 1.2-.4 1.2 .6 .5 1 1.3 .6 .9-.7 1.3-.2 1.9 .8 .8 4-1.2 2.3-.7 3.2 1.2 2.4-.1 1.1-1.3 0-1.9-.6-1.8 .1-3.3 .5-1.9 .8-2.8 1-.5-.1 .2-2.3 .3-.3-.1-1.1-1.2-1.2-.9-.2-.8-.8 .6-1.2-.3-1.3 .2-.9 .4 0 .2-1.2-.3-.5 .3-.4 1-.3-.6-2.3-.7-1.1 .2-1z',
|
||||
},
|
||||
CL: {
|
||||
name: 'Chile',
|
||||
path: 'M309.3 477.5l.1 8.5 2.9 0 1.7 .1-.9 1.6-2.4 1.2-1.4-.1-1.6-.4-2-1.1-2.9-.6-3.5-2.1-2.9-2.1-3.8-4.2 2.3 .8 3.9 2.5 3.7 1.4 1.4-1.8 .9-2.6 2.6-1.5zM306.7 365.4l1.4 1.9 .3 2.1 1.5 1.2-.9 2.8 1.5 3.3 1.1 4 2-.4 .3 .7-.9 3.1-3 1.4 0 5-.5 .9 .8 1.2-2 1.8-1.8 2.9-1 2.7 .3 2.9-1.7 3.2 1.2 5.3 .8 .6 0 2.8-1.6 3.1 0 2.7-2.1 2 0 3 .9 3.1-1.7 1.2-.7 2.9-.7 3.4 .5 4-1.1 .7 .6 3.9 1.3 1.2-.9 1.5 1.3 .7 .3 1.2-1.3 .7 .3 2-1 4.6-1.4 3 .3 1.8-.9 2.3-2.1 1.6 .2 3.8 1 1.3 1.8-.2 0 2.7 1.1 2.2 6.7 .5 2.6 .6-2.4-.1-1.4 1-2.5 1.3-.4 3.5-1.2 .1-3.1-1.2-3.2-2.6-3.5-2.2-.9-2.3 .8-2.2-1.4-2.4-.3-6.2 1.2-3.4 2.9-2.7-4.2-1 2.6-3.1 1-5.7 3.1 1.2 1.4-7-1.9-.9-.8 4.2-1.8-.5 .9-4.8 .9-6.1 1.3-2.2-.8-3.2-.2-3.6 1.2-.1 1.7-5.1 1.9-5 1.2-4.7-.7-4.6 .8-2.5-.3-3.7 1.6-3.7 .5-5.8 .9-6.1 .9-6.5-.2-4.8-.6-4 1.4-.8z',
|
||||
},
|
||||
CM: {
|
||||
name: 'Cameroon',
|
||||
path: 'M540.3 280.1l1.1 1.8 .2 1.9-.1 1.9 1.5 2.5-1.6 0-.8 .2-1.2-.3-.6 1.3 1.6 1.7 1.2 .4 .4 1.2 .9 1.9-.5 .8-1.4 2.8-.6 .5-.2 2.2 .2 1.2-.2 .8 1.3 1.4 .3 1 1 1.5 1.3 .9 .1 1.2 .3 .8-.2 1.5-2.2-.6-2.3-.8-3.5-.1-.3-.1-1.7 .3-1.7-.3-1.3 .2-4.5-.1 .4-2.2-1.1-1.8-1.2-.5-.6-1.3-.7-.4 0-.7 .7-2 1.3-2.7 .9 0 1.6-1.6 1.1-.1 1.5 1.2 1.9-1 .3-1.1 .6-1.2 .4-1.4 1.5-1.1 .6-2 .6-.6 .4-1.5 .7-1.8 2.3-2.1 .2-1 .3-.5-1.1-1.1 .1-.9z',
|
||||
},
|
||||
CN: {
|
||||
name: 'China',
|
||||
path: 'M804.1 264.9l-2.3-.9-.1-2.4 1.4-1.3 3-.8 1.6 0 .7 1.1-1.3 1.3-.6 1.7zM722.9 190.9l-.2-1.9 1.9-.9-2.5-5.9 5.5-1.4 1.5-.8 2-6.2 5.5 1.2 1.5-1.6 .1-3.6 2.4-.3 2.1-2.4 1.1-.3 .7 2.5 2.3 1.9 4 1.3 1.9 2.8-1.1 4.1 1 1.5 3.3 .6 3.8 .5 3.3 2.1 1.7 .4 1.3 3.1 1.6 2 3.1-.1 5.7 .8 3.7-.5 2.8 .5 4.1 2.1 3.3 0 1.3 1 3.2-1.8 4.5-1.2 4.2-.1 3.2-1.2 2-1.8 1.9-1.1-.4-1.1-.9-1.3 1.5-2.3 1.5 .4 2.9 .7 2.7-1.9 4.3-1.3 2-2.3 2-1 4-.5 2.2 .4 .3-1.2-2.5-2.5-2.2-1.1-2.2 1.3-2.7-.5-1.6 .4-.7-1.4 2-3.6 1.3-2.7 3.3 1.3 4-2.3-.1-1.6 2.5-3.9 1.6-1.1-.1-2.1-1.5-.9 2.3-1.9 3.5-.6 3.7-.1 4.1 1.1 2.5 1.4 1.7 3.7 1 1.6 1 2.3 1 3.5 4.8 1.2 3.3 2.5 1.2 3.4 4.2 0 2.4-1.4 4.6-1.1-1.5 3.2-1.1 1.3-.9 3.8-1.9 3.4-3.4-.6-2.3 1.2 .7 2.9-.4 4-1.4 .1 0 1.7-1.8-2-1.1 1.9-4.3 1.5 .4 1.7-2.4-.1-1.3-1-1.9 2.3-3.1 1.8-2.2 2.1-3.9 .9-2 1.6-3 .8 1.4-1.5-.5-1.2 2.2-2.2-1.5-1.8-2.4 1.2-3.2 2.3-1.7 2.1-2.7 .2-1.4 1.5 1.4 2.2 2.3 .5 .1 1.4 2.2 1 3.1-2.3 2.5 1.2 1.8 .1 .4 1.7-3.9 .9-1.3 1.7-2.7 1.6-1.4 2.2 3 1.7 1.1 3.1 1.6 2.8 1.9 2.4 0 2.3-1.8 .8 .7 1.6 1.6 1-.4 2.4-.7 2.4-1.5 .3-2.1 3.2-2.2 3.9-2.6 3.6-3.8 2.7-3.9 2.5-3.1 .3-1.7 1.3-1-1-1.5 1.5-3.9 1.5-3 .4-.9 3.1-1.6 .1-.7-2.1 .7-1.1-3.7-.9-1.4 .4-2.8-.7-1.3-1.2 .5-1.7-2.6-.5-1.3-1.1-2.4 1.6-2.7 .3-2.2 0-1.5 .7-1.4 .4 .4 3.3-1.5 0-.2-.7-.1-1.2-2.1 .8-1.2-.5-2-1.1 .8-2.4-1.8-.6-.6-2.7-3 .5 .4-3.5 2.6-2.5 .1-2.4-.1-2.3-1.2-.7-.9-1.8-1.6 .2-3-.4 .9-1.3-1.3-1.9-2 1.3-2.3-.8-3.2 2-2.5 2.2-2.3 .4-1.2-.8-1.5-.1-2-.7-1.5 .8-1.8 2.2-.2-2.4-1.7 .7-3.3-.3-3.1-.7-2.3-1.3-2.1-.6-.9-1.5-1.6-.4-2.8-2-2.2-.9-1.2 .7-3.9-2.1-2.7-2-.8-3.4 2 .4 .1-1.5-1.1-1.6 .3-2.6-3-3.7-4.6-1.2-.8-2.5-2-1.5-.5-.9-.4-1.8 .1-1.3-1.7-.7-.9 .3-.7-3 .7-.7-.3-.8 2.6-1.5 1.9-.7 3 .5 1-2.2 3.6-.3 1-1.4 4.4-1.8z',
|
||||
},
|
||||
CO: {
|
||||
name: 'Colombia',
|
||||
path: 'M314.2 312.6l-.5 .3-.5-1.6-.8-.9-.9 .9-5.4 0 0 1.7 1.6 .3-.1 1.1-.5-.3-1.6 .4 0 2.1 1.2 1 .5 1.6-.1 1.2-1.2 7.6-1.4-1.5-.9-.1 1.8-2.8-2.1-1.3-1.7 .2-1-.4-1.5 .7-2.1-.3-1.6-3-1.3-.7-.9-1.3-1.8-1.3-.8 .3-1.2-.7-1.3-.9-.8 .4-2.4-.4-.6-1.2-.6 .1-2.7-1.6-.4-.9 1-.2-.1-1.4 .6-1 1.4-.2 1.2-1.7 1.1-1.5-1.1-.6 .6-1.6-.7-2.6 .6-.7-.4-2.4-1.1-1.5 .3-1.3 .9 .2 .5-.8-.6-1.7 .3-.4 1.5 .1 2-2 1.2-.3 0-.9 .5-2.4 1.6-1.3 1.8 0 .2-.6 2.2 .2 2.2-1.4 1-.6 1.4-1.4 1 .2 .7 .7-.5 1-1.8 .5-.7 1.4-1.1 .8-.8 1-.4 2-.7 1.6 1.4 .2 .4 1.3 .6 .6 .2 1.2-.3 1 .1 .6 .6 .2 .7 1 3.6-.3 1.6 .4 2 2.4 1.1-.3 2 .1 1.6-.3 .9 .5-.5 1.5-.6 .9-.2 2 .6 1.9 .8 .8 0 .6-1.4 1.4 1 .6 .8 1z',
|
||||
},
|
||||
CR: { name: 'Costa Rica', path: 'M270.7 289.4l-1.1 .2 0 1.2 .6 .4-.4 .3 .1 .5-.2 .6-.2 .5-1.5-.6-.5-.6 .3-.5-.1-.6-.8-.6-1.1-.6-.9-.4-.2-.8-.7-.5 .1 .8-.5 .7-.7-.8-.8-.2-.4-.6 0-.9 .4-.8-.8-.4 .6-.6 .4-.3 1.9 .7 .6-.4 .9 .3 .4 .5 .9 .2 .6-.6 .7 1.6 1.1 1.1z' },
|
||||
CU: { name: 'Cuba', path: 'M271.5 250.5l2.4 .2 2.2 0 2.6 1 1.1 1.1 2.6-.4 1 .7 2.3 1.8 1.7 1.4 .9-.1 1.7 .6-.2 .8 2.1 .1 2 1.2-.3 .7-1.8 .4-1.9 .1-1.9-.2-4 .3 1.9-1.6-1.2-.8-1.7-.2-1-.8-.7-1.7-1.5 .1-2.6-.8-.9-.6-3.6-.4-.9-.6 1-.7-2.7-.2-2 1.6-1.2 0-.4 .7-1.4 .3-1.1-.3 1.4-.9 .6-1 1.3-.7 1.4-.5 2.1-.3z' },
|
||||
CY: { name: 'Cyprus', path: 'M590.9 214.3l.5 .1 .8-.2 .5 0 .2 .2 .1 .3 .1-.1 .4 0 .6-.2 .3 .1 .1 .3-2.9 1.2-1.3-.4-.7-1.2z' },
|
||||
CZ: { name: 'Czech Republic', path: 'M541.7 160.2l1.3 1.2 2.1 .3-.2 1 1.5 .8 .5-1 1.9 .4 .2 1.2 2.1 .2 1.3 1.8-.9 0-.4 .6-.6 .2-.2 .8-.5 .2-.1 .3-1 .4-1.2-.1-.4 .8-1.3-.7-1.3 .2-2.1-1.1-1 .3-1.6 1.5-2-1.2-1.6-1.5-1.4-.9-.3-1.5-.5-1.1 2-.8 1.1-.9 1.9-.7 .7-.7 .8 .4z' },
|
||||
DE: {
|
||||
name: 'Germany',
|
||||
path: 'M539.2 150.3l.7 1.9-.8 1.1 1 1.3 .7 2-.2 1.3 1.1 2.3-1.2 .4-.8-.4-.7 .7-1.9 .7-1.1 .9-2 .8 .5 1.1 .3 1.5 1.4 .9 1.6 1.5-1 1.7-1 .4 .4 2.3-.3 .6-.8-.7-1.4-.1-2 .6-2.4-.1-.4 .9-1.4-1-.8 .2-3-1.1-.6 .8-2.4 0 .4-2.6 1.4-2.4-4-.7-1.3-.9 .1-1.6-.5-.8 .3-2.5-.5-3.8 1.7 0 .7-1.4 .7-3.5-.5-1.2 .5-.8 2.3-.2 .6 .8 1.8-1.9-.6-1.4-.1-2.2 2.1 .5 1.8-.6 0 1.5 2.8 .9 0 1.4 2.8-.8 1.6-1 3.1 1.5z',
|
||||
},
|
||||
DJ: { name: 'Djibouti', path: 'M617.6 281l1.2 .3 .9-.7 .6 .8-.1 1.2-1.5 .7 1.1 .8-1 1.5-.6-.5-.7 .2-1.5-.1-.1-.8-.2-.8 1-1.3z' },
|
||||
DK: { name: 'Denmark', path: 'M527.6 145.6l-1.8 .6-2.1-.5-1.1-2.1-.1-4 .4-1.1 .8-1.2 2.5-.3 1-1.1 2.2-1.1-.1 2.1-.8 1.3 .3 1.1 1.5 .6-.7 1.5-.8-.5-2 2.8zM534.4 141.2l.9 2-1.7 3.1-2.9-2.1-.4-1.7z' },
|
||||
DO: { name: 'Dominican Republic', path: 'M300.8 265.4l.1-.8-.7-.9 .6-.5 .2-1.1-.2-1.5 .3-.5 2.2 0 1.7 .7 .7 0 .5 1 1.5-.1-.1 .9 1.3 .1 1.3 1-1 1.2-1.3-.6-1.3 .1-.9-.1-.5 .5-1.1 .2-.4-.7-.9 .4-1.1 2-.7-.5z' },
|
||||
DZ: {
|
||||
name: 'Algeria',
|
||||
path: 'M475.9 238l0-.6 0-.2 0-3.5 4.5-2.3 2.8-.4 2.2-.8 1.1-1.6 3.2-1.2 .2-2.2 1.6-.3 1.2-1.2 3.7-.5 .5-1.2-.8-.6-.9-3.3-.2-1.9-1-2 2.6-1.8 3-.5 1.8-1.3 2.7-1 4.7-.6 4.6-.2 1.4 .5 2.6-1.3 3 0 1.1 .7 1.9-.2-.6 1.7 .5 3-.7 2.6-1.7 1.7 .2 2.4 2.3 1.8 0 .8 1.8 1.2 1.1 5.5 .9 2.7 .2 1.4-.5 2.5 .2 1.3-.4 1.7 .3 1.9-1.1 1.2 1.6 2.2 .1 1.2 1 1.7 1.3-.6 2.2 1.4 1.2 1.8-9.5 5.6-8 5.7-3.9 1.3-3.1 .3-.1-1.9-1.2-.4-1.8-.9-.6-1.3-9.4-6.4-9.4-6.4z',
|
||||
},
|
||||
EC: { name: 'Ecuador', path: 'M290.6 316.5l.4 2.1-.8 1.8-3.1 2.9-3.3 1.1-1.7 2.4-.5 1.9-1.6 1.1-1.2-1.4-1.1-.3-1.2 .2 0-1 .8-.6-.4-1.2 1.5-2.1-.6-1.2-1 1.3-1.7-1.2 .6-.8-.5-2.5 1-.4 .5-1.8 1-1.8-.2-1.1 1.5-.6 2-1.1 2.7 1.6 .6-.1 .6 1.2 2.4 .4 .8-.4 1.3 .9z' },
|
||||
EE: { name: 'Estonia', path: 'M577.7 127.7l.4 .7-1.9 2.4 .8 3.8-1.2 1.2-2.3 0-2.4-1.5-1.2-.4-2.4 .7 .4-2.4-1.1 .5-1.7-1.4-.3-2.4 3.5-1.1 3.5-.6 3.1 .6z' },
|
||||
EG: {
|
||||
name: 'Egypt',
|
||||
path: 'M602.4 253.9l-11 0-10.8 0-11.2 0 0-10.8 0-10.6-.8-2.5 .7-1.9-.4-1.3 1-1.4 3.7-.1 2.7 .8 2.7 .9 1.3 .5 2.2-1 1.1-.8 2.4-.3 2 .4 .8 1.5 .6-1 2.2 .7 2.2 .2 1.4-.8 1.5 4.5 .3 .8-.8 1.2-.6 2.3-.7 1.5-.7 .6-.9-1-1.3-1.4-1.9-4.3-.3 .3 1.1 3.2 1.7 3 2.1 4.7 1.1 1.6 .9 1.6 2.4 3.3-.5 .5 .1 1.9 3.2 2.6z',
|
||||
},
|
||||
EH: { name: 'Western Sahara', path: 'M475.9 237.2l0 .2 0 .6 0 4.5-9.1-.1 0 7.5-2.6 .3-.6 1.5 .5 4.2-10.9 0-.6 1 .1-1.3 .1 0 6.2-.2 .4-1.1 1.1-1.3 .9-4 3.9-3.2 1.3-3.7 .8-.2 1-2.4 2.3-.3 1 .4 1.3 0 .9-.7 1.7-.1-.1-1.6z' },
|
||||
ER: { name: 'Eritrea', path: 'M601.2 275.7l-.3-1.1 1.2-4.2 .3-1.9 .8-.9 2.1-.5 1.4-1.6 1.6 3.3 .8 2.6 1.5 1.4 3.8 2.7 1.5 1.6 1.5 1.7 .9 .9 1.4 .9-.9 .7-1.2-.3-.9-.9-1.1-1.7-1.3-.9-.7-.9-2.4-1.2-1.9 0-.7-.6-1.6 .7-1.7-1.3-.9 2.1z' },
|
||||
ES: {
|
||||
name: 'Spain',
|
||||
path: 'M479.3 208l-.2-1 1-1.2 .4-.9-1-1 .8-2.1-1.1-1.9 1.2-.3 .1-1.5 .4-.5 .1-2.6 1.3-.9-.8-1.7-1.6-.1-.5 .4-1.7 0-.7-1.6-1.1 .5-1 .8 .1-2.3-1.1-1.5 3.9-2.4 3.4 .6 3.8 0 2.9 .5 2.3-.1 4.5 .1 1.1 1.3 5.1 1.5 1-.7 3.2 1.5 3.2-.4 .1 1.9-2.6 2.2-3.5 .7-.3 1.1-1.7 1.8-1.1 2.7 1.1 1.9-1.6 1.4-.6 2.1-2.1 .6-2 2.5-3.5 0-2.6 0-1.8 1.1-1 1.2-1.4-.3-1-1-.8-1.9z',
|
||||
},
|
||||
ET: {
|
||||
name: 'Ethiopia',
|
||||
path: 'M632.7 293.8l-7.8 8.3-3.6 .2-2.5 1.9-1.8 .1-.7 .8-1.9 0-1.2-.9-2.5 1.2-.8 1.1-1.9-.2-.6-.3-.6 .1-.9-.1-3.5-2.3-2 0-.9-.9 0-1.6-1.4-.5-1.7-3-1.3-.7-.4-1.1-1.5-1.3-1.7-.2 1-1.6 1.5-.1 .4-.8-.1-2.6 .9-2.9 1.3-.8 .3-1.1 1.1-2.2 1.7-1.4 1.2-2.8 .4-2.4 3.2 .6 .9-2.1 1.7 1.3 1.6-.7 .7 .6 1.9 0 2.4 1.2 .7 .9 1.3 .9 1.1 1.7 .9 .9-.9 1.3-1 1.3 .2 .8 .1 .8 1.5 .1 .7-.2 .6 .5-.6 1 1 1.5 1.1 1.4 1 1 9.1 3.3z',
|
||||
},
|
||||
FI: {
|
||||
name: 'Finland',
|
||||
path: 'M579.4 84.9l-.4 3.4 4.3 3.2-2.6 3.5 3.2 5.3-1.8 3.9 2.5 3.3-1.2 2.9 4.1 3-1 2.2-2.6 2.5-5.9 5.4-5.1 .3-4.9 1.5-4.5 .9-1.6-2.3-2.7-1.4 .6-4.1-1.3-3.9 1.3-2.5 2.5-2.8 6.4-4.8 1.9-1-.3-1.9-3.9-2.2-.9-1.7-.1-7.3-4.3-3.2-3.8-2.4 1.7-1.3 3.1 2.6 3.6-.3 3 1.2 2.7-2.2 1.3-3.6 4.3-1.6 3.6 1.9z',
|
||||
},
|
||||
FJ: { name: 'Fiji', path: 'M1000 361l0 1.4-1.8 .7-1.7 .6-.4-1 1.4-.6 .9-.2zM994.8 365.2l.7-.5 .9 .8-.4 1.5-1.7 .4-1.6-.4-.2-1.2 1-1zM.6 360.9l-.4 1.4-.2 .1 0-1.4z' },
|
||||
FK: { name: 'Falkland Islands / Malvinas', path: 'M330 474.6l3.3-2.2 2.4 .9 1.7-1.5 2.2 1.7-.8 1.3-3.8 1.1-1.2-1.3-2.4 1.7z' },
|
||||
FR: {
|
||||
name: 'France',
|
||||
path: 'M356.5 304.5l-1.6 2.5-.9 2.1-1.1 1-1.3 .2-.4-.7-.6-.2-.8 .8-1.3-.6 .8-1.2 .2-1.2 .5-1.2-1.1-1.7-.2-1.9 1.4-2.4 1 .3 2 .7 2.9 2.3zM517.2 166.2l1.3 .9 4 .7-1.4 2.4-.4 2.6-.7 .6-1.3-.3 .1 .8-2 2-.1 1.6 1.4-.5 .9 1.5-.1 1 .8 1.2-1 1.1 .8 2.6 1.5 .5-.3 1.5-2.6 1.9-5.4-1-4.1 1.1-.3 2.1-3.2 .4-3.2-1.5-1 .7-5.1-1.5-1.1-1.3 1.5-2.1 .5-6.8-2.9-3.7-2-1.7-4.3-1.4-.3-2.6 3.6-.8 4.7 .9-.9-4 2.7 1.5 6.4-2.8 .9-3 2.4-.7 .4 1.3 1.3 0 1.3 1.5 1.9 1.7 1.4-.3 2.5 1.7 .6 .3zM524.3 189.9l1.8-1.2 .5 2.8-1 2.6-1.2-.7-.7-2.2z',
|
||||
},
|
||||
GA: { name: 'Gabon', path: 'M531.3 309.8l1.3-.2 1.7 .3 1.7-.3 .3 .1-.2 1.3 .8 1.4 2.1-.2 .7 .5-1.2 3.2 1.3 1.7 .3 2.1-.4 1.9-.8 1.3-2.5-.1-1.5-1.3-.2 1.2-1.9 .3-.9 .7 1 1.9-2.1 1.5-2.8-2.8-1.9-2.3-1.7-2.9 .1-.9 .6-.9 .7-2 .6-2.1 .9-.1 4 0z' },
|
||||
GB: {
|
||||
name: 'United Kingdom',
|
||||
path: 'M482.8 149.9l-2.1-.8-1.7 .1 .5-2.1-.5-2 2.3-.2 3 2.4zM491.4 151.7l.4-2.2-1.8-2.4-.1-.1-3.4-.6-.6-1.1 1-1.7-.9-1.1-1.5 1.9-.2-3.8-1.4-2 1-4.1 2.2-3.3 2.2 .3 3.4-.3-3 4.3 2.8-.5 3.1 0-.8 3.2-2.5 3.6 2.9 .2 .2 .4 2.5 4.6 1.9 .6 1.7 4.4 .8 1.4 3.4 .8-.4 2.4-1.4 1 1.1 2-2.5 1.9-3.7-.1-4.7 1-1.3-.7-1.8 1.7-2.6-.4-2 1.4-1.4-.7 4-3.9 2.5-.8-4.3-.6-.8-1.4 2.9-1.2-1.6-2 .6-2.5z',
|
||||
},
|
||||
GE: { name: 'Georgia', path: 'M611 187.2l.3-.4 2.4 .6 4.1 .6 3.7 1.6 .5 .6 1.7-.5 2.6 .7 .9 1.3 1.7 .8-.7 .5 1.3 1.8-.3 .4-1.5-.2-2.1-1-.7 .5-3.8 .6-2.7-1.7-3 .2 .4-1.4-.7-2.3-1.6-1.3-1.5-.3z' },
|
||||
GH: { name: 'Ghana', path: 'M500.1 285.3l-.2 .9 1.1 1.4 0 2.1 .3 2.2 .7 1-.6 2.5 .2 1.4 .7 1.8 .6 1-4.3 1.6-1.6 .9-2.5 .8-2.4-.7 .1-1.1-1.2-2.4 .7-3.2 1.2-2.3-.8-4-.3-2.1 0-1.6 4.9-.2 1.2 .2 .9-.4z' },
|
||||
GL: {
|
||||
name: 'Greenland',
|
||||
path: 'M370.1 7.1l9.3-4.1 9.8 .3 3.5-2.6 9.8-.7 22.2 .9 17.4 5.5-5.1 2.6-10.7 .3-14.9 .7 1.4 1.2 9.8-.7 8.4 2.3 5.4-2.1 2.3 2.4-3.1 3.9 7.1-2.5 13.5-2.6 8.3 1.3 1.6 2.9-11.3 4.6-1.6 1.5-8.9 1.1 6.4 .3-3.2 4.6-2.2 4 .1 6.7 3.3 3.8-4.4 .3-4.5 1.8 5.1 3.1 .7 4.8-3 .5 3.6 4.7-6.2 .4 3.2 2.2-.9 1.9-3.9 .8-3.9 .1 3.5 3.5 .1 2.4-5.5-2.2-1.5 1.4 3.8 1.3 3.6 3.2 1.1 4.1-5 .9-2.1-1.9-3.5-2.9 1 3.4-3.2 2.7 7.3 .2 3.8 .2-7.4 4.3-7.6 3.9-8.1 1.6-3.1 0-2.8 1.9-3.9 4.9-6 3.3-1.9 .2-3.7 1.1-4 1.1-2.4 2.8 0 3.1-1.4 2.9-4.5 3.5 1.1 3.3-1.3 3.6-1.4 4.1-3.9 .2-4.1-3.4-5.6 0-2.7-2.3-1.8-4.2-4.8-5.4-1.4-2.9-.4-4.1-3.9-4.2 1-3.4-1.8-1.6 2.7-5.6 4.2-1.8 1.1-2 .6-3.9-3.2 1.8-1.5 .7-2.5 .7-3.4-1.6-.2-3.4 1.1-2.6 2.6-.1 5.6 1.3-4.7-3.2-2.5-1.7-2.8 .7-2.3-1.3 3.1-4.8-1.7-2-2.2-3.7-3.3-5.8-3.6-2.1 .1-2.4-7.5-3.3-5.9-.4-7.4 .2-6.8 .5-3.2-1.9-4.8-3.6 7.3-1.8 5.5-.4-11.8-1.5-6.3-2.4 .4-2.4 10.5-2.9 10.2-3 1-2.3-7.5-2.2 2.5-2.6 9.6-4.5 4-.7-1.1-3 6.5-1.8 8.6-1 8.5-.1 3.1 2.1 7.3-3.7 6.7 2.5 3.9 .5 5.7 2.2-6.6-3.6z',
|
||||
},
|
||||
GM: { name: 'The Gambia', path: 'M453.6 278l3 0 .6-.7 .9-.1 1.1 .7 .9 .1 .9-.5 .5 .8-1.2 .6-1.2 0-1.2-.6-1 .6-.5 .1-.7 .4-2.5-.1z' },
|
||||
GN: {
|
||||
name: 'Guinea',
|
||||
path: 'M461.9 280.9l1.4 0 2 .7 .6-.1 .2-.3 1.5 .3 .4-.2 .2 1 .4 0 .7-.3 .5 0 .8 .8 1.2 .2 .7-.6 .9-.4 .7-.4 .5 .1 .7 .6 .3 .8 1.1 1.2-.5 .7-.1 .9 .5-.3 .4 .3-.2 .9 .9 .8-.6 .2-.2 1 .7 1.1 .6 2.3-1 .3-.3 .4 .3 .5-.2 1.2-.4 0-.8 0-.6 1.1-.8 0-.5-.6 .2-1.1-1.2-1.7-.7 .3-.6 0-.8 .2 0-1-.4-.8 .1-.8-.6-1.2-.8-1-2.2 0-.7 .6-.7 0-.5 .6-.3 .8-1.5 1.2-1.2-1.6-1.1-1.1-.7-.4-.7-.5-.3-1.3-.4-.6-.8-.5 1.2-1.3 .8 0 .8-.4 .6 0 .4-.4-.2-1 .3-.2z',
|
||||
},
|
||||
GQ: { name: 'Equatorial Guinea', path: 'M526.8 309.7l4.5 .1 0 3.3-4 0-.9 .1-.6-.4z' },
|
||||
GR: {
|
||||
name: 'Greece',
|
||||
path: 'M573 213.8l-.3 .9-4 .2 0-.5-3.4-.6 .5-1.3 1.6 1 2.1-.1 2.1 .2-.1 .5zM563.8 194.2l2 .1 2.2-.9 2 1.2 2.5-.3 0-1.7 1.4 .9-.9 2.1-.6 .3-1.7 0-1.5-.4-3.3 .9 1.9 1.8-1.4 .6-1.6 0-1.4-1.7-.5 .7 .6 2 1.4 1.5-1.1 .7 1.6 1.5 1.3 .9 .1 1.9-2.6-.9 .8 1.6-1.7 .4 1 2.8-1.8 0-2.3-1.3-1-2.6-.5-2.2-1.1-1.4-1.4-1.9-.2-.9 1.3-1.6 .1-1.1 .9-.4 .1-.9 1.8-.3 1.1-.7 1.5 0 .4-.5z',
|
||||
},
|
||||
GT: { name: 'Guatemala', path: 'M243.8 275.4l.1-.9 .3-.6-.4-.6 1.3-2.3 3.6 0 .1-.9-.5-.2-.3-.6-1-.7-1-1 1.2 0 0-1.6 2.6 0 2.6 .1 0 2.2-.3 3.2 .9 0 .9 .6 .2-.5 .8 .4-1.2 1.1-1.4 .8-.1 .5 .2 .6-.6 .7-.7 .2 .2 .3-.5 .3-1 .7-.1 .4-1.4-.4-1.7-.1-1.3-.6z' },
|
||||
GW: { name: 'Guinea-Bissau', path: 'M453.7 281.5l1.4-.5 1 .1 .7-.3 5.1 .1 0 1-.3 .2 .2 1-.4 .4-.6 0-.8 .4-.8 0-1.2 1.3-1.5-1.1-1.2-.2-.6-.8 0-.4-.8-.6z' },
|
||||
GY: { name: 'Guyana', path: 'M342.9 310.8l-.6 .1-1.6-.3-.9 .8-1.2 .5-.9 .1-.3 .5-1.4-.1-1.7-1.3-.2-1.3-.7-1.4 .4-2.4 .8-1-.6-1.3-1-.4 .4-1.2-.7-.6-1.4 .1-1.9-2.1 .8-.8-.1-1.3 1.7-.4 .7-.6-.9-1 .2-1 2.2-1.7 1.8 1.1 1.7 1.8 .1 1.4 1.1 .1 1.5 1.4 1.1 .9-.5 2.5-1.7 .8 .2 .6-.5 1.5 1.2 2 .9 0 .3 1.5z' },
|
||||
HN: {
|
||||
name: 'Honduras',
|
||||
path: 'M269 274.1l-.9-.1-.4 .4-1 .4-.7 0-.6 .3-.5-.1-.5-.4-.3 .1-.4 .6-.2 0-.1 .6-.9 .8-.6 .3-.2 .4-.9-.6-.6 .7-.5 0-.7 .1 .1 1.4-.4 0-.4 .7-.8 .1-.5-.9-.9-.3 .2-1.1-.4-.3-.5-.2-1.2 .3-.1-.3-.9-.5-.6-.6-.8-.2 .6-.7-.2-.6 .1-.5 1.4-.8 1.2-1.1 .3 .1 .6-.5 .8 0 .3 .2 .4-.1 1.3 .2 1.3-.1 .9-.3 .3-.3 .9 .2 .7 .1 .7 0 .5-.3 1.3 .4 .4 .1 .9 .5 .8 .7 1 .4z',
|
||||
},
|
||||
HR: { name: 'Croatia', path: 'M546 176.7l.9 .4 2.1 1.5 2.3 .7 1-.5 .7 1.3 .9 1-1.1 1.3-1.3-.8-1.9 .1-2.4-.6-1.3 .1-.6 .7-1-.8-.5 1.4 1.3 1.6 .6 1.1 1.3 1.2 1 .8 1.1 1.4 2.5 1.3-.3 .5-2.7-1.2-1.6-1.2-2.5-1-2.3-2.5 .5-.3-1.3-1.4 0-1.2-1.8-.5-.8 1.5-.9-1.2 .1-1.2 .1 0 1.9 .1 .5-.6 1 .6 1.1 0 0-.9 .9-.4 .3-1.4z' },
|
||||
HT: { name: 'Haiti', path: 'M300.8 260.6l.2 1.5-.2 1.1-.6 .5 .7 .9-.1 .8-1.8-.5-1.3 .2-1.7-.2-1.3 .5-1.5-.9 .2-.9 2.6 .4 2.1 .2 1-.6-1.3-1.3 0-1.1-1.7-.4 .6-.8 1.7 .1z' },
|
||||
HU: { name: 'Hungary', path: 'M561.3 169.9l1.6 1 .2 .9-1.7 .8-1.3 2.4-1.7 2.3-2.2 .7-1.8-.2-2.1 1-1 .5-2.3-.7-2.1-1.5-.9-.4-.5-1.2-.5 0 .9-2.3-.5-.8 1.6 0 .2-1.4 1.4 .9 1 .4 2.3-.4 .3-.8 1.1-.1 1.3-.5 .3 .2 1.3-.4 .7-.9 .9-.2 3 1.1z' },
|
||||
ID: {
|
||||
name: 'Indonesia',
|
||||
path: 'M891.7 323.3l0 9 .1 9.1-2.5-2.3-2.8-.5-.7 .8-3.5 .1 1.1-2.3 1.8-.8-.7-3-1.4-2.4-5.4-2.3-2.2-.3-4.2-2.5-.8 1.3-1.1 .3-.6-1.1 0-1.2-2.2-1.3 3-1 2 0-.2-.7-4.1 0-1.1-1.7-2.5-.5-1.1-1.4 3.7-.6 1.4-.9 4.5 1.1 .4 1 .8 4.5 2.9 1.7 2.3-3 3.2-1.6 2.4 0 2.4 .9 2.1 1zM847.1 340.8l.3 .6 .1 .8-1.8 2.1-2.4 .6-.4-.3 .3-1 1.2-1.7zM872.8 335.2l-.3-2.1 .5-1 .6-.9 .6 .8 0 1.3zM827.5 304.5l-1.6 2.6 2 2.6-.5 1.3 3.1 2.5-3.2 .4-1 1.9 .1 2.5-2.6 1.9-.1 2.7-1.1 4.3-.4-1-3.1 1.3-1.1-1.7-2-.2-1.4-.9-3.3 1-1-1.3-1.8 .1-2.3-.3-.4-3.7-1.4-.8-1.4-2.4-.4-2.4 .4-2.6 1.6-1.8 .5 1.8 1.9 1.6 1.8-.6 1.7 .2 1.7-1.4 1.3-.2 2.6 .8 2.3-.6 1.4-3.9 1.1-1 .9-3.1 3.2 0zM859.4 323.8l3 .8 1 2.2-2.3-1.2-2.3-.2-1.6 .2-1.9-.1 .6-1.6zM852.4 326.6l-1.9-.5-.5-1.2 2.8-.2 .7 1zM855.4 310l.2 1.5 1.6 .3 .3 1.1-.2 2.4-1.4-.3-.4 1.7 1.1 1.5-.8 .3-1.1-1.7-.8-3.6 .5-2.2zM841.5 313.6l3.2-.1 2.7-2 .5 .6-2.2 2.7-2.1 .6-2.7-.6-4.6 .2-2.5 .4-.4 2.1 2.5 2.5 1.5-1.3 5.2-1-.2 1.3-1.2-.4-1.2 1.7-2.5 1 2.7 3.6-.6 .9 2.5 3.3 0 1.8-1.5 .8-1.1-1 1.4-2.3-2.7 1.1-.7-.7 .3-1.1-2-1.7 .2-2.7-1.8 .9 .2 3.2 .1 4-1.7 .4-1.2-.8 .8-2.6-.5-2.7-1.1 0-.9-1.9 1.2-1.8 .4-2.2 1.3-4.2 .6-1.1 2.4-2.1 2.2 .8zM834.2 344.6l-3.7-1.9 2.6-.6 1.4 .9 1 .8-.2 .8zM837.1 339.8l1.8-.2 2.5-1-.4 1.5-4.2 .8-3.7-.3 0-1 2.2-.6zM828.5 339.3l1.7-.2 .7 1.2-3.2 .5-1.9 .4-1.5 0 .9-1.6 1.6 0 .7-1zM801.4 333.9l.3 1 5.4 .3 .6-1.2 5.1 1.4 1 1.8 4.2 .5 3.4 1.6-3.2 1.1-3-1.1-2.5 .1-2.9-.2-2.6-.6-3.2-1-2.1-.3-1.1 .4-5.1-1.2-.5-1.2-2.5-.2 1.9-2.7 3.4 .2 2.2 1.1zM789.9 319.1l.5 1.9 1 1.5 2 .3 1.3 1.7-.7 3.5-.1 4.3-3 .1-2.4-2.4-3.5-2.2-1.2-1.7-2.1-2.3-1.4-2.1-2.1-3.9-2.5-2.3-.8-2.4-1-2.1-2.5-1.8-1.5-2.4-2.1-1.5-2.9-3.1-.2-1.4 1.8 .1 4.3 .6 2.4 2.7 2.2 1.9 1.5 1.1 2.7 3 2.8 0 2.3 2 1.6 2.3 2.1 1.2-1.1 2.3 1.6 1z',
|
||||
},
|
||||
IE: { name: 'Ireland', path: 'M482.8 149.9l.4 2.7-2.1 3.4-4.9 2.1-3.9-.5 2.2-3.9-1.4-3.8 3.8-3 2.1-1.8 .5 2-.5 2.1 1.7-.1z' },
|
||||
IL: { name: 'Israel', path: 'M599.2 221.8l-.5 1-1-.4-.5 2 .6 .4-.7 .4-.1 .8 1.3-.4 .1 1.2-1.4 4.9-.3-.8-1.5-4.5 .8-1-.2-.2 .7-1.4 .6-2.3 .4-.8 .1 0 .9 0 .3-.6 .7 0 0 1.3z' },
|
||||
IN: {
|
||||
name: 'India',
|
||||
path: 'M770.4 235.4l.2 1.2-1 .5 .2 1.8-2-.5-3.6 2.1 .1 1.7-1.5 2.5-.2 1.4-1.2 2.4-2.2-.6-.1 3-.6 1 .3 1.2-1.4 .7-1.4-4.6-.8 0-.5 1.9-1.5-1.6 .9-1.6 1.2-.2 1.3-2.5-1.6-.5-2.6 0-2.6-.4-.3-2-1.3-.2-2.2-1.3-1 2.1 2 1.5-1.7 1.1-.6 1.1 1.7 .8-.5 1.8 1 2.2 .4 2.4-.4 1-1.9 0-3.4 .6 .2 2.2-1.5 1.7-4 2-3.1 3.3-2.1 1.8-2.8 1.9 0 1.3-1.4 .7-2.5 1.1-1.3 .1-.8 2.2 .6 3.7 .1 2.3-1.2 2.7 0 4.8-1.4 .1-1.3 2.1 .9 1-2.6 .8-.9 1.9-1.1 .8-2.6-2.6-1.3-4-1.1-2.8-1-1.3-1.4-2.7-.7-3.6-.5-1.7-2.5-4-1.2-5.5-.8-3.7 0-3.5-.5-2.7-4.1 1.8-1.9-.4-3.7-3.5 1.4-1.1-.9-1.1-3.2-2.5 1.8-2 6.1 0-.5-2.5-1.6-1.5-.3-2.3-1.8-1.3 3.1-3.2 3.2 .3 2.9-3.2 1.7-3.1 2.7-3 0-2.2 2.4-1.8-2.3-1.5-1-2.1-.9-2.8 1.3-1.3 4.2 .8 3.1-.5 2.7-2.7 3 3.7-.3 2.6 1.1 1.6-.1 1.5-2-.4 .8 3.4 2.7 2 3.9 2.1-1.8 1.4-1 2.8 2.7 1.1 2.6 1.5 3.6 1.7 3.8 .4 1.6 1.5 2.2 .3 3.3 .7 2.3-.1 .3-1.1-.3-1.9 .2-1.3 1.7-.7 .2 2.4 .1 .6 2.5 1.1 1.7-.4 2.4 .2 2.2-.1 .2-1.9-1.1-.9 2.3-.4 2.5-2.2 3.2-2 2.3 .8 2-1.3 1.3 1.9-.9 1.3z',
|
||||
},
|
||||
IQ: { name: 'Iraq', path: 'M608.9 223.5l-1.1-3.7 6.1-3.3 1.1-3.8-.3-2.3 1.5-.8 1.4-2 1.2-.5 3.3 .4 .9 .9 1.4-.6 1.8 3.8 1.8 1 .2 1.8-1.4 1.1-.6 2.4 1.9 3 3.4 1.7 1.4 2.3-.4 2.3 .8 0 .1 1.6 1.5 1.6-1.6-.2-1.9-.2-2 2.9-5.2-.3-7.8-6.1-4.2-2.1z' },
|
||||
IR: {
|
||||
name: 'Iran',
|
||||
path: 'M634.9 230.4l-1.5-1.6-.1-1.6-.8 0 .4-2.3-1.4-2.3-3.4-1.7-1.9-3 .6-2.4 1.4-1.1-.2-1.8-1.8-1-1.8-3.8-1.6-2.6 .6-1-.9-3.7 1.9-.9 .5 1.2 1.4 1.5 1.9 .5 1-.1 3.3-2.4 1-.3 .8 1-.9 1.6 1.7 1.7 .7-.2 .9 2.4 2.6 .7 1.9 1.6 4 .5 4.3-.8 .3-.8 2.4-.6 2-1.8 1.9 .1 1.2-.6 2 .3 3 1.6 2.2 .3 3.2 2.9 2.1 .1 .2 2.6-1.1 4-.8 2.2 1.2 .5-1.1 1.7 .9 2.5 .2 1.9 2.1 .5 .2 2-2.5 2.8 1.4 1.6 1.1 1.8 2.6 1.3 .1 2.7 1.3 .4 .3 1.4-4 1.6-1.1 3.4-5.2-.9-3-.7-3.2-.4-1.1-3.6-1.4-.5-2.1 .5-2.8 1.4-3.4-.9-2.8-2.3-2.7-.9-1.8-2.9-2.1-4-1.5 .5-1.8-1z',
|
||||
},
|
||||
IS: { name: 'Iceland', path: 'M459.7 97.3l-.6 3 3.1 3.1-3.6 3.4-8 3-2.4 .8-3.7-.6-7.7-1.4 2.7-2-6-2.2 4.9-.9-.1-1.3-5.9-1 1.9-3 4.2-.7 4.3 3.1 4.3-2.5 3.5 1.3 4.5-2.5z' },
|
||||
IT: {
|
||||
name: 'Italy',
|
||||
path: 'M529 175.3l1.7 .5 .3-.6 2.8-.6 .6 1.2 4 .9-.3 1.7 .6 1.5-2.2-.5-2.3 1.2 .2 1.7-.3 1 .9 1.7 2.6 1.7 1.4 2.8 3.1 2.7 2.1 0 .7 .7-.8 .7 2.5 1.2 2.1 1 2.3 1.7 .3 .6-.5 1.2-1.5-1.6-2.4-.5-1.2 2.1 2 1.2-.3 1.7-1.2 .2-1.5 2.8-1.1 .2 0-.9 .5-1.8 .6-.7-1-1.9-.9-1.6-1.1-.4-.9-1.4-1.7-.6-1.2-1.4-2.1-.2-2.2-1.5-2.5-2.1-1.9-2-.9-3.3-1.3-.4-2.3-1.1-1.3 .4-1.6 1.6-1.1 .3 .3-1.5-1.5-.5-.8-2.6 1-1.1-.8-1.2 .1-1 1.2 .7 1.3-.1 1.6-1.2 .5 .5 1.3-.1 .6-1.4 2.1 .5 1.2-.6zM541 204.7l2.1-.3-1 2.5 .4 1-.6 1.7-2.1-1.2-1.4-.4-3.9-1.6 .4-1.7 3.3 .3zM524.2 195.7l1.4-1 1.6 2.3-.3 4.3-1.3-.2-1.1 1.1-1.1-.8-.1-4-.6-1.9z',
|
||||
},
|
||||
JM: { name: 'Jamaica', path: 'M284.5 264.1l1.9 .3 1.5 .6 .4 .8-1.9 .1-.9 .5-1.5-.5-1.6-1 .3-.7 1.2-.2z' },
|
||||
JO: { name: 'Jordan', path: 'M598.7 222.8l.5-1 3.1 1.3 5.5-3.3 1.1 3.7-.6 .5-5.5 1.5 2.8 3.1-1 .5-.4 1-2.1 .5-.7 1.1-1.2 .9-3.1-.5-.1-.4 1.4-4.9-.1-1.2 .4-.9z' },
|
||||
JP: {
|
||||
name: 'Japan',
|
||||
path: 'M894.1 201.3l-2.5 3.3 0 3.3-1 2.5 .4 1.6-1.4 2.3-3.6 1.4-4.8 .2-4 3.6-1.9-1.2-.1-2.3-4.8 .7-3.3 1.4-3.2 .1 2.8 2.3-1.9 5.2-1.8 1.3-1.3-1.2 .7-2.7-1.8-.9-1.1-2.2 2.6-.9 1.5-2 2.7-1.6 2.1-2.2 5.5-.9 3 .6 2.9-5.6 1.8 1.5 4.1-3.2 1.6-1.2 1.7-4-.4-3.7 1.1-2.1 3-.6 1.5 4.6zM901.7 185.4l2-1.4 .6 3.8-4.1 .9-2.5 3.4-4.3-2.3-1.5 3.6-3.1 .1-.4-3.3 1.4-2.6 2.9-.2 .8-4.7 .9-2.7 3.2 3.6 2.2 1.1zM867.7 219.5l1.5-1.9 1.6 .4 1.2-1.3 2 .7 .4 1-1.6 1.9-1.2-1-1.4 .7-.7 1.9-1.8-.9z',
|
||||
},
|
||||
KE: { name: 'Kenya', path: 'M608.9 329l-4-2.7-.2-1.7-10.1-5.6-.4-.3-.1-3 .8-1.1 1.4-1.8 1-2.1-1.2-3.1-.3-1.4-1.3-2 1.7-1.6 1.9-1.9 1.4 .5 0 1.6 .9 .9 2 0 3.5 2.3 .9 .1 .6-.1 .6 .3 1.9 .2 .8-1.1 2.5-1.2 1.2 .9 1.9 0-2.5 3.2 .1 10.1 1.6 2.3-1.9 1.1-.7 1.2-1.1 .2-.4 1.9-.8 1.2-.6 1.8z' },
|
||||
KG: { name: 'Kyrgyzstan', path: 'M697.1 191.2l.6-1.5 1.9-.5 4.5 1.2 .5-2 1.5-.7 4 1.4 1-.4 4.6 .1 4.1 .4 1.4 1.2 1.7 .5-.3 .7-4.4 1.8-1 1.4-3.6 .3-1 2.2-3-.5-1.9 .7-2.6 1.5 .3 .8-.7 .7-5.3 .5-3.4-1.1-3 .3 .2-1.9 3 .6 1.1-1 2.1 .3 3.5-2.4-3.3-1.7-1.9 .8-2.1-1.3 2.3-2.1z' },
|
||||
KH: { name: 'Cambodia', path: 'M785 282l-.7-3.4 1.8-2.3 3.6-.6 2.6 .4 2.3 1.1 1.2-1.9 2.5 1 .6 1.9-.3 3.4-4.7 2.2 1.2 1.7-2.9 .2-2.4 1.1-2.3-.4-1.1-1.5z' },
|
||||
KP: {
|
||||
name: 'Dem. Rep. Korea',
|
||||
path: 'M862.9 190.7l.4 .6-1.1-.2-1.2 1.1-.8 1.2 .1 2.4-1.4 .7-.5 .6-1.1 .9-1.8 .6-1.2 .9-.1 1.4-.4 .3 1.2 .6 1.5 1.4-.4 .8-1.2 .2-1.9 .1-1.1 1.5-1.2-.1-.2 .3-1.4-.7-.3 .6-.8 .3-.1-.6-.7-.3-.8-.5 .8-1.4 .6-.4-.2-.6 .7-1.7-.2-.6-1.6-.3-1.3-.9 2.2-2.1 3.1-1.8 1.9-2.3 1.3 1 2.4 .1-.4-1.7 4.3-1.5 1.1-1.9z',
|
||||
},
|
||||
KR: { name: 'Republic of Korea', path: 'M850.5 206l.2-.3 1.2 .1 1.1-1.5 1.9-.1 1.2-.2 .4-.8 2.4 3.8 .7 2 0 3.7-1 1.7-2.5 .6-2.2 1.3-2.6 .3-.3-1.7 .6-2.4-1.3-3.3 2.1-.5z' },
|
||||
KW: { name: 'Kuwait', path: 'M633.3 230.2l.5 1.4-.2 .7 .9 2.2-2 .1-.7-1.4-2.4-.3 2-2.9z' },
|
||||
KZ: {
|
||||
name: 'Kazakhstan',
|
||||
path: 'M742.7 167.1l-2.1 2.4-2.4 .3-.1 3.6-1.5 1.6-5.5-1.2-2 6.2-1.5 .8-5.5 1.4 2.5 5.9-1.9 .9 .2 1.9-1.7-.5-1.4-1.2-4.1-.4-4.6-.1-1 .4-4-1.4-1.5 .7-.5 2-4.5-1.2-1.9 .5-.6 1.5-1.6 .6-3.6 2.3-1.3 2.4-1 0-.8-1.6-3.5-.1-.5-2.7-1.4 0 .2-3.4-3.3-2.5-4.8 .3-3.2 .5-2.7-3.1-2.3-1.3-4.3-2.4-.5-.3-7.1 2 .1 12.4-1.5 .2-1.9-2.6-1.9-.9-3.1 .7-1.3 1.1-.1-.8 .7-1.4-.6-1.2-3.2-1.2-1.2-3-1.6-.9-.1-1.1 2.7 .4 .1-2.6 2.4-.5 2.4 .5 .5-3.4-.5-2.1-2.7 .1-2.4-.8-3.2 1.5-2.6 .8-1.4-.6 .3-1.8-1.8-2.4-2.1 .1-2.3-2.4 1.6-2.7-.8-.7 2.2-4 2.8 2.1 .4-2.6 5.7-4 4.4-.1 6.1 2.5 3.3 1.5 2.9-1.5 4.4-.1 3.6 1.9 .8-1.1 3.9 .2 .7-1.8-4.5-2.5 2.6-1.8-.5-1.1 2.7-.9-2-2.6 1.3-1.3 10.4-1.4 1.3-.9 7-1.4 2.5-1.6 4.9 .8 .9 4 2.9-.9 3.6 1.3-.2 2 2.6-.2 7-3.6-1 1.2 3.5 3 6.2 9.4 1.5-1.9 3.8 2.1 4-.9 1.6 .6 1.3 2.1 1.9 .7 1.2 1.6 3.6-.5z',
|
||||
},
|
||||
LA: { name: 'Lao PDR', path: 'M798.3 276.3l-2.5-1-1.2 1.9-2.3-1.1 .9-1.3 .1-2.4-2.2-2.4-.2-2.9-2.1-2.3-2.1-.2-.6 1-1.6 .1-.9-.5-2.9 1.7 0-2.6 .6-3-1.8-.1-.2-1.8-1.2-.9 .6-1 2.4-1.9 .2 .7 1.5 0-.4-3.3 1.4-.4 1.6 2.3 1.3 2.6 3.4 .1 1.1 2.5-1.8 .7-.8 1.1 3.3 1.7 2.3 3.4 1.8 2.5 2.1 2 .7 2z' },
|
||||
LB: { name: 'Lebanon', path: 'M599.5 220.1l-.7 0-.3 .6-.9 0 1-2.6 1.3-2.2 .1-.1 1.2 .2 .5 1.2-1.5 1.2z' },
|
||||
LK: { name: 'Sri Lanka', path: 'M727.2 295.1l-.4 2.9-1.2 .8-2.4 .6-1.3-2.2-.5-4 1.2-4.5 2 1.5 1.2 2z' },
|
||||
LR: { name: 'Liberia', path: 'M476.6 294.6l-.2 .9 .3 1.3-.6 1.2 .8 .8 .9 .2 1.2 1.2 .1 1.1-.3 .3-.2 2.3-.8 0-2.8-1.3-2.5-2.1-2.4-1.5-1.9-1.8 .7-.9 .1-.8 1.3-1.6 1.3-1.3 .6 0 .7-.3 1.2 1.7-.2 1.1 .5 .6 .8 0 .6-1.1z' },
|
||||
LS: { name: 'Lesotho', path: 'M580.5 398.8l1 .9-.9 1.4-.5 1-1.5 .5-.5 1-1 .3-2.1-2.4 1.5-1.9 1.5-1.2 1.3-.6z' },
|
||||
LT: { name: 'Lithuania', path: 'M573.6 143.2l.3 1.7-2.3 1.3-.7 2.1-3 1.5-2.7-.1-.6-1.1-1.5-.5-.2-.9 .3-1.1-1.2-.6-2.9-.7-.6-3.2 3.2-1.3 4.6 .3 2.8-.4 .3 .8 1.5 .3z' },
|
||||
LU: { name: 'Luxembourg', path: 'M516.8 163.8l.5 .8-.1 1.6-.8 .1-.6-.3 .3-2.1z' },
|
||||
LV: { name: 'Latvia', path: 'M575.8 135.8l1.3 1 .3 1.9 .9 2.3-3 1.5-1.7 .7-2.7-1.9-1.5-.3-.3-.8-2.8 .4-4.6-.3-3.2 1.3 .1-3 1.3-2.5 2.7-1.4 2.2 3 2.2-.1 .5-3 2.4-.7 1.2 .4 2.4 1.5z' },
|
||||
LY: {
|
||||
name: 'Libya',
|
||||
path: 'M569.4 253.9l0 5.8-3.1 0-.1 1.3-11.1-5.6-11-5.6-2.8 1.6-2 1.1-1.6-1.6-4.4-1.3-1.2-1.8-2.2-1.4-1.3 .6-1-1.7-.1-1.2-1.6-2.2 1.1-1.2-.3-1.9 .4-1.7-.2-1.3 .5-2.5-.2-1.4-.9-2.7 1.4-.7 .2-1.3-.3-1.2 1.9-1.2 .9-1 1.4-.9 .1-2.4 3.3 1.1 1.1-.3 2.4 .5 3.6 1.4 1.3 2.8 2.5 .5 4 1.3 2.9 1.5 1.4-.7 1.3-1.5-.6-2.3 .8-1.5 2-1.4 1.9-.5 3.8 .7 .9 1.3 1.1 .1 .9 .5 2.7 .3 .7 1.1-1 1.4 .4 1.3-.7 1.9 .8 2.5 0 10.6z',
|
||||
},
|
||||
MA: {
|
||||
name: 'Morocco',
|
||||
path: 'M494 214.2l1 2 .2 1.9 .9 3.3 .8 .6-.5 1.2-3.7 .5-1.2 1.2-1.6 .3-.2 2.2-3.2 1.2-1.1 1.6-2.2 .8-2.8 .4-4.5 2.3 0 3.5-.4 0 .1 1.6-1.7 .1-.9 .7-1.3 0-1-.4-2.3 .3-1 2.4-.8 .2-1.3 3.7-3.9 3.2-.9 4-1.1 1.3-.4 1.1-6.2 .2-.1 0 .2-1.3 1-.8 .9-1.5-.2-1 1-2.1 1.5-1.9 1-.4 .7-1.8 .1-1.5 1-1.9 1.8-1.1 1.8-3 .1-.1 1.3-1.1 2.6-.3 2.2-2.1 1.4-.8 2.3-2.5-.7-3.8 1.1-2.7 .4-1.6 1.8-2.1 2.7-1.4 2.1-1.3 1.9-3.3 .8-1.9 2.1 0 1.6 1.4 2.7-.3 2.9 .7z',
|
||||
},
|
||||
MD: { name: 'Moldova', path: 'M573.9 170.6l.7-.5 1.9-.3 2 1.1 1.1 .1 1.3 1-.2 1.2 1 .5 .4 1.5 1 .9-.2 .5 .5 .4-.7 .2-1.7-.1-.2-.5-.6 .3 .2 .6-.8 1.2-.5 1.1-.7 .4-.5-1.6 .3-1.4-.1-1.6-1.6-2.1-.9-1.5-.8-1z' },
|
||||
ME: { name: 'Montenegro', path: 'M555.8 190.1l-.8 .3-.2-.7-1.2 1.7 .2 1.1-.6-.3-.7-1.1-1.2-.7 .3-.5 .4-1.9 .9-.8 .5-.3 .7 .6 .4 .5 .9 .3 1.1 .7-.2 .3z' },
|
||||
MG: {
|
||||
name: 'Madagascar',
|
||||
path: 'M637.6 350.9l.8 1.2 .6 1.8 .5 3.4 .7 1.4-.3 1.3-.5 .9-.9-1.7-.5 .8 .5 2.1-.2 1.3-.8 .6-.2 2.4-1.1 3.4-1.3 4-1.8 5.5-1 4-1.3 3.5-2.2 .7-2.5 1.2-1.6-.7-2.2-1.1-.7-1.6-.2-2.6-1-2.3-.2-2.1 .4-2.1 1.3-.5 0-1 1.4-2.2 .2-1.8-.6-1.4-.6-1.8-.2-2.6 1-1.6 .4-1.8 1.3-.1 1.6-.6 1-.5 1.2-.1 1.6-1.6 2.3-1.7 .8-1.5-.3-1.2 1.1 .4 1.6-2 0-1.7 1-1.3z',
|
||||
},
|
||||
MK: { name: 'North Macedonia', path: 'M562.2 191l1.4 1 .2 2.2-.6 .2-.4 .5-1.5 0-1.1 .7-1.8 .3-1.2-.8-.4-1.4 .4-1.2 .3 .1 .2-.7 1.6-.5 .6-.2 1-.2z' },
|
||||
ML: {
|
||||
name: 'Mali',
|
||||
path: 'M468 281.3l.1-.9-.2-1.1-1-.8-.6-1.6-.1-1.7 .9-.6 .5-1.6 .9-.1 1.9 .8 1.6-.6 1.1 .2 .4-.6 11.1-.1 .6-2-.4-.3-1.4-12.4-1.3-12.6 4.2-.1 9.4 6.4 9.4 6.4 .6 1.3 1.8 .9 1.2 .4 .1 1.9 3.1-.3 0 6.6-1.6 1.9-.2 1.7-2.5 .5-3.8 .2-1 1.1-1.8 .1-1.7 0-.7-.6-1.6 .4-2.6 1.2-.5 .9-2.1 1.3-.4 .7-1.2 .6-1.3-.4-.8 .7-.4 1.9-2.2 2.4 .1 .9-.8 1.2 .2 1.6-1.2 .4-.6 .4-.4-1.2-.8 .3-.5 0-.5 .8-2.2 0-.7-.5-.4 .3-.9-.8 .2-.9-.4-.3-.5 .3 .1-.9 .5-.7-1.1-1.2-.3-.8-.7-.6-.5-.1-.7 .4-.9 .4-.7 .6-1.2-.2-.8-.8-.5 0-.7 .3-.4 0z',
|
||||
},
|
||||
MM: {
|
||||
name: 'Myanmar',
|
||||
path: 'M778.1 258.5l-1.6 .7-1.6 1.3-2 .1-1.2 3.1-1.2 .5 1.3 2.5 1.8 2.1 1.1 1.9-1 2.5-.9 .5 .6 1.4 1.9 2.3 .3 1.6-.1 1.3 1.1 2.6-1.5 2.6-1.3 2.9-.3-2.1 .8-2.2-.9-1.6 .2-3.1-1.1-1.5-.9-3.4-.5-3.6-1.2-2.3-1.8 1.4-3.2 2-1.5-.2-1.8-.7 1-3.5-.6-2.7-2.2-3.3 .4-1.1-1.6-.3-2-2.4-.2-2.3 1 .4 0-2.1 1.4-.7-.3-1.2 .6-1 .1-3 2.2 .6 1.2-2.4 .2-1.4 1.5-2.5-.1-1.7 3.6-2.1 2 .5-.2-1.8 1-.5-.2-1.2 1.6-.2 .9 1.8 1.2 .7 .1 2.3-.1 2.4-2.6 2.5-.4 3.5 3-.5 .6 2.7 1.8 .6-.8 2.4 2 1.1 1.2 .5 2.1-.8 .1 1.2-2.4 1.9z',
|
||||
},
|
||||
MN: {
|
||||
name: 'Mongolia',
|
||||
path: 'M743.8 166.8l2.9-.6 5.3-3.1 4.2-1.8 2.4 1.2 2.9 0 1.9 1.7 2.8 .1 4 1 2.7-2.6-1.2-2.1 2.9-3.9 3.1 1.6 2.5 .4 3.3 1 .5 2.7 4 1.5 2.6-.6 3.5-.5 2.8 .5 2.7 1.7 1.7 1.9 2.6-.1 3.5 .6 2.5-.9 3.7-.6 4.1-2.5 1.6 .4 1.5 1.2 3.3-.3-1.3 2.7-2 3.6 .7 1.4 1.6-.4 2.7 .5 2.2-1.3 2.2 1.1 2.5 2.5-.3 1.2-2.2-.4-4 .5-2 1-2 2.3-4.3 1.3-2.7 1.9-2.9-.7-1.5-.4-1.5 2.3 .9 1.3 .4 1.1-1.9 1.1-2 1.8-3.2 1.2-4.2 .1-4.5 1.2-3.2 1.8-1.3-1-3.3 0-4.1-2.1-2.8-.5-3.7 .5-5.7-.8-3.1 .1-1.6-2-1.3-3.1-1.7-.4-3.3-2.1-3.8-.5-3.3-.6-1-1.5 1.1-4.1-1.9-2.8-4-1.3-2.3-1.9z',
|
||||
},
|
||||
MR: { name: 'Mauritania', path: 'M452.6 256.9l.6-1 10.9 0-.5-4.2 .6-1.5 2.6-.3 0-7.5 9.1 .1 0-4.5 10.4 7.2-4.2 .1 1.3 12.6 1.4 12.4 .4 .3-.6 2-11.1 .1-.4 .6-1.1-.2-1.6 .6-1.9-.8-.9 .1-.5 1.6-.9 .6-1.8-2-1.7-2.1-1.9-.7-1.3-.9-1.5 0-1.4 .7-1.4-.3-.9 .9-.3-1.5 .8-1.4 .3-2.7-.3-2.8-.3-1.5 .3-1.4-.7-1.4z' },
|
||||
MW: { name: 'Malawi', path: 'M591 341.8l2.7 .5 .6 .7 .9 1.4 .8 3.8-.8 2.1 .8 3.7 1-.1 1 1 1.1 2 .3 3.7-1.2 .6-.9 1.9-1.8-1.7-.2-2 .6-1.3-.2-1.2-1.1-.7-.7 .3-1.6-1.4-1.5-.7 .8-2.7 .9-.9-.5-2.4 .5-2.3 .5-.7-.7-2.4z' },
|
||||
MX: {
|
||||
name: 'Mexico',
|
||||
path: 'M174.6 222.4l3.2-.3 3.5-.3-.2 .6 4.2 1.5 6.3 2.2 5.5 0 2.2 0 0-1.3 4.8 0 1.1 1.1 1.4 .9 1.6 1.4 1 1.6 .6 1.7 1.5 .9 2.3 .9 1.7-2.4 2.3-.1 2 1.2 1.4 2.1 .9 1.8 1.7 1.7 .6 2.1 .7 1.4 2.2 .9 2 .7 1.1-.1-1.1 2.6-.5 2.1-.2 3.9-.3 1.4 .5 1.6 .9 1.5 .5 2.2 1.9 2.2 .6 1.6 1.1 1.4 3 .8 1.1 1.2 2.4-.8 2.2-.3 2-.5 1.8-.5 1.8-1.2 .6-1.7 .2-2.4 .5-.8 1.9-.8 2.9-.7 2.5 .1 1.7-.2 .7 .6-.1 1.4-1.5 1.7-.7 1.8 .5 .5-.4 1.2-.7 2.3-.7-.8-.6 .1-.5 0-1 1.7-.5-.3-.3 .1 0 .5-2.6-.1-2.6 0 0 1.6-1.2 0 1 1 1 .7 .3 .6 .5 .2-.1 .9-3.6 0-1.3 2.3 .4 .6-.3 .6-.1 .9-3.1-3.1-1.5-.9-2.2-.8-1.6 .3-2.2 1-1.4 .3-2-.7-2.1-.6-2.6-1.3-2-.4-3.2-1.3-2.3-1.4-.7-.8-1.6-.1-2.8-.9-1.2-1.3-2.9-1.7-1.4-1.8-.7-1.4 .9-.3-.3-.8 .7-.8 0-1-.9-1.3-.3-1.1-.9-1.5-2.5-2.9-2.8-2.3-1.3-1.9-2.4-1.2-.5-.7 .4-1.8-1.4-.7-1.6-1.5-.7-2.1-1.5-.2-1.6-1.6-1.3-1.5-.2-.9-1.5-2.3-.9-2.3 0-1.2-2-1.2-.9 .1-1.6-.8-.5 1.2 .5 1.5 .3 2.3 .9 1.2 2.1 2.1 .4 .7 .5 .2 .3 1.1 .5-.1 .6 2 .8 .7 .6 1.1 1.8 1.5 .9 2.8 .8 1.3 .8 1.4 .1 1.5 1.4 .1 1.1 1.3 1 1.4-.1 .5-1.2 1.1-.4-.1-.8-1.7-1.8-1.7-2-1.4-1.4-.8 .1-2.2-.4-1.6-1.4-.9-1.9-1.3-.3 .4-.7-.8-1.8-.7-1.6-1.8 .2-.2 1.1 .2 1.1-1.1 .1-1.4-2.2-2.2-1.6-.8-1-1.9-1-2-1.3-2.4z',
|
||||
},
|
||||
MY: {
|
||||
name: 'Malaysia',
|
||||
path: 'M778 298.1l.5-.5 2.3 1.2 .2 1.4 1.8-.3 .9-1.2 .7 .3 1.6 1.7 1.2 1.8 .1 1.9-.3 1.3 .3 .9 .2 1.7 1 .7 1.1 2.5-.1 .9-1.9 .2-2.7-2-3.3-2.2-.3-1.4-1.6-1.9-.4-2.3-1-1.5 .3-2.1zM827.5 304.5l-2.5-.4-3.2 0-.9 3.1-1.1 1-1.4 3.9-2.3 .6-2.6-.8-1.3 .2-1.7 1.4-1.7-.2-1.8 .6-1.9-1.6-.5-1.8 2.1 .9 2.1-.5 .6-2.4 1.1-.5 3.4-.6 2-2.2 1.3-1.7 1.3 1.4 .6-.9 1.3 0 .2-1.7 .1-1.4 2.1-1.9 1.4-2.2 1.2 0 1.4 1.4 .1 1.2 1.8 .8 2.4 .8-.2 1.1-1.9 .1 .5 1.4z',
|
||||
},
|
||||
MZ: {
|
||||
name: 'Mozambique',
|
||||
path: 'M596 348.2l2.1-.3 3.3 .8 .8-.3 1.9-.1 1-.8 1.6 0 3.1-1.1 2.2-1.6 .4 1.3-.1 2.8 .4 2.4 .1 4.4 .5 1.4-.9 2.1-1 1.9-1.8 1.8-2.5 1.1-3.2 1.4-3.1 3.1-1.1 .5-1.9 2-1.2 .7-.2 2.1 1.3 2.2 .6 1.7 0 .8 .5-.1-.1 2.9-.4 1.3 .6 .5-.4 1.2-1.2 1.1-2.3 1-3.3 1.6-1.2 1.1 .2 1.2 .7 .2-.2 1.6-2.1 0-.3-1.3-.4-1.4-.2-1 .5-3.3-.7-2.1-1.4-4.1 3-3.3 .7-2.1 .4-.3 .3-1.7-.4-.8 .1-2.2 .5-2 0-3.6-1.4-.9-1.3-.2-.6-.7-1.3-.6-2.3 0-.2-1-.3-2.1 8.5-2.3 1.6 1.4 .7-.3 1.1 .7 .2 1.2-.6 1.3 .2 2 1.8 1.7 .9-1.9 1.2-.6-.3-3.7-1.1-2-1-1-1 .1-.8-3.7z',
|
||||
},
|
||||
NA: {
|
||||
name: 'Namibia',
|
||||
path: 'M555.3 386.3l0 11-2.5 1.5-1.5 .2-1.8-.5-1.2-.3-.5-1.3-1.1-.8-1.3 1.5-2.1-2.3-1-2.1-.7-2.9-.6-2.2-1-4.5 0-3.5-.4-1.6-1.1-1.2-1.4-2.4-1.5-3.5-.6-1.8-2.2-2.8-.2-2.2 1.3-.6 1.7-.5 1.8 .1 1.7 1.3 .4-.2 11.2-.1 2 1.4 6.7 .4 5.1-1.2 2.3-.6 1.8 .1 1.1 .7 0 .2-1.6 .6-.8 .1-1.8 1.1-1.1-1.2-4.2 1-2.1 .1-.1 10.3-2.7 .1z',
|
||||
},
|
||||
NC: { name: 'New Caledonia', path: 'M960.5 375.5l2.3 1.8 1.4 1.3-1 .7-1.6-.8-1.9-1.3-1.8-1.5-1.9-2.1-.4-1 1.2 .1 1.6 1 1.2 .9z' },
|
||||
NE: {
|
||||
name: 'Niger',
|
||||
path: 'M541.3 251.4l.6 4.6 1.1 .7 0 .9 1.2 1-.6 1.3-1.1 5.8-.1 3.7-3.6 2.7-1.2 3.8 1.2 1 0 1.8 1.7 .1-.2 1.3-.8 .2-.1 .9-.5 0-1.9-3-.7-.2-2.1 1.6-2.2-.8-1.5-.2-.8 .4-1.6-.1-1.6 1.2-1.5 .1-3.3-1.4-1.3 .7-1.5-.1-1-1.1-2.8-1-3 .3-.7 .6-.4 1.7-.8 1.1-.2 2.5-2.1-1.6-1 0-.9 .8 0-1.9-3.2-.7 0-1.3-1.6-1.9-.4-1.3 .2-1.3 1.8-.1 1-1.1 3.8-.2 2.5-.5 .2-1.7 1.6-1.9 0-6.6 3.9-1.3 8-5.7 9.5-5.6 4.4 1.3 1.6 1.6z',
|
||||
},
|
||||
NG: {
|
||||
name: 'Nigeria',
|
||||
path: 'M507.5 298.6l.1-4.5 0-1.7 .5-1.8 .8-.9 1.4-1.7-.3-.8 .5-1.1-.6-1.7 .1-.9 .2-2.5 .8-1.1 .4-1.7 .7-.6 3-.3 2.8 1 1 1.1 1.5 .1 1.3-.7 3.3 1.4 1.5-.1 1.6-1.2 1.6 .1 .8-.4 1.5 .2 2.2 .8 2.1-1.6 .7 .2 1.9 3 .5 0 1.1 1.1-.3 .5-.2 1-2.3 2.1-.7 1.8-.4 1.5-.6 .6-.6 2-1.5 1.1-.4 1.4-.6 1.2-.3 1.1-1.9 1-1.5-1.2-1.1 .1-1.6 1.6-.9 0-1.3 2.7-.7 2-2.9 1-1-.2-1.1 .7-2.2-.1-1.5-1.7-.9-2.1-2-1.8-2.1 0z',
|
||||
},
|
||||
NI: {
|
||||
name: 'Nicaragua',
|
||||
path: 'M267.6 285.5l-.6 .6-.9-.2-.4-.5-.9-.3-.6 .4-1.9-.7-.4 .3-1-.9-1.2-1.1-.7-1-1.1-.8-1.4-1.3 .3-.5 .4 .5 .3-.2 .8-.1 .4-.7 .4 0-.1-1.4 .7-.1 .5 0 .6-.7 .9 .6 .2-.4 .6-.3 .9-.8 .1-.6 .2 0 .4-.6 .3-.1 .5 .4 .5 .1 .6-.3 .7 0 1-.4 .4-.4 .9 .1-.2 .2-.1 .7 .2 1-.6 1-.3 1.1-.1 1.3 .2 .7 0 1.3-.4 .2-.3 1.2 .2 .8-.5 .7 .1 .8z',
|
||||
},
|
||||
NL: { name: 'Netherlands', path: 'M519.2 151.4l.5 1.2-.7 3.5-.7 1.4-1.7 0 .5 3.8-1.5-.8-1.8-1.6-2.6 .7-2-.3 1.4-1 2.5-5.5 3.8-1.5z' },
|
||||
NO: {
|
||||
name: 'Norway',
|
||||
path: 'M542.1 26.5l1-2.2 4.1-.2 3.5 2.2 9.1 4.6-6.9 2.4-1.6 4.5-2.4 1.1-1.3 4.9-3.4 .2-6-3.6 2.5-2.1-4.1-1.7-5.4-5.1-2.2-4.9 7.6-2.3 1.5 2.2zM586.4 82.5l-4.7 1.9-2.3 .5 1.2-3.5-3.6-1.9-4.3 1.6-1.3 3.6-2.7 2.2-3-1.2-3.6 .3-3.1-2.6-1.7 1.3-1.7 .2-.4 3.2-5.2-.8-.8 2.7-2.6-.1-1.9 3.4-2.7 5.2-4.3 6.4 1 1.5-1 1.8-2.8-.1-1.8 4.1 .2 5.8 1.8 2.1-.9 5-2.3 2.8-1.3 2.4-1.8-2.6-5.5 4.8-3.7 .9-3.9-2-1-4.4-.8-9.8 2.5-2.7 7.4-3.7 5.4-4.6 5.1-6.3 6.7-9 4.7-3.6 7.6-6.1 6.1-2.2 4.6 .3 4.2-4.2 5.1 .2 4.9-1 8.7 3.7-3.6 1.4zM576.1 24l-4.1 3.4-8 .8-8.2-1.1-.5-1.7-4-.1-3.1-2.9 8.6-1.8 4.1 1.5 2.8-1.9 7 1.6zM568.7 37.6l-6.2 2.4-4.9-1.3 1.9-1.6-1.7-1.9 5.8-1.2 1.1 2.3z',
|
||||
},
|
||||
NP: { name: 'Nepal', path: 'M744.8 236.6l-.2 1.3 .3 1.9-.3 1.1-2.3 .1-3.3-.7-2.2-.3-1.6-1.5-3.8-.4-3.6-1.7-2.6-1.5-2.7-1.1 1-2.8 1.8-1.4 1.2-.7 2.2 .9 2.8 2 1.6 .4 .9 1.5 2.1 .6 2.3 1.3 3.1 .7z' },
|
||||
NZ: {
|
||||
name: 'New Zealand',
|
||||
path: 'M991.3 433.6l-1 1.8-1.4 2.3-2.1 1.3-.5-.9-1.2-.5 1.6-2.7-.9-1.8-3-1.3 .1-1.2 2-1.1 .5-2.5-.1-2.1-1.2-2.1 .1-.6-1.3-1.3-2.2-2.8-1.2-2.2 1.1-.2 1.5 1.7 2.1 .8 .8 2.8 2 3.4 .1-2.2 1.3 .9 .4 2.4 2.2 1 1.9 .3 1.6-1.2 1.4 .3-.7 2.9-.8 1.9-2.2-.1-.7 1 .3 1.4zM971.3 445.3l2.4-1.8 1.6-1.8 1.3-2.4 1-.9 .4-1.8 2-1.6 .6 1.4 .6 1.4 2-1.3 .8 1.4 0 1.4-1 1.5-1.8 2.5-1.4 1.3 1 1.7-2.2 0-2.3 1.3-.8 2.2-1.6 3.5-2.1 1.6-1.4 1-2.6-.1-1.8-1.2-3-.2-.5-1.3 1.5-2.5 3.5-3.4 1.8-.7z',
|
||||
},
|
||||
OM: {
|
||||
name: 'Oman',
|
||||
path: 'M653.4 251.9l0-1.2 .8-1.2 0-1.2 1.3-.6-.5-.4 .2-1.9 1.5 0 1.2 2 1.6 1 2 .4 1.6 .6 1.3 1.6 .7 1 1 .4 0 .6-1 1.8-.4 .8-1.2 .9-1 2-1.3-.1-.6 .6-.4 1.5 .3 1.9-.2 .4-1.3 0-1.8 1.1-.2 1.4-.7 .6-1.7-.1-1.1 .8 0 1.1-1.3 .8-1.5-.3-1.9 1-1.3 .2-.9-2-2.2-4.8 8.4-2.9 1.8-5.8zM656.3 243l-.5-1 .8-1 .3 .3-.3 1.2z',
|
||||
},
|
||||
PA: {
|
||||
name: 'Panama',
|
||||
path: 'M285.1 291.9l-.3 .4 .6 1.7-.5 .8-.9-.2-.3 1.3-1-.8-.6-1.5 .7-.7-.7-.2-.5-.9-1.4-.8-1.2 .2-.6 .9-1.1 .7-.6 .1-.3 .6 1.4 1.5-.8 .4-.4 .4-1.3 .1-.5-1.6-.3 .5-.9-.2-.6-1.1-1.1-.2-.8-.3-1.2 0 0 .6-.4-.5 .2-.5 .2-.6-.1-.5 .4-.3-.6-.4 0-1.2 1.1-.2 1 1-.1 .6 1.2 .1 .2-.2 .8 .7 1.3-.2 1.2-.7 1.7-.6 1-.8 1.5 .1-.1 .3 1.5 .1 1.3 .5 .9 .8z',
|
||||
},
|
||||
PE: {
|
||||
name: 'Peru',
|
||||
path: 'M305.9 328l-2.6-.1-.3 .4-2.3 .5-3.2 1.9-.2 1.3-.7 1 .3 1.5-1.7 .8 0 1.2-.7 .5 1.1 2.5 1.6 1.7-.6 1.2 1.8 .2 1.1 1.5 2.4 0 2.3-1.6-.2 4.2 1.3 .4 1.6-.5 2.4 4.5-.6 1-.2 2 0 2.4-1.1 1.4 .5 1-.6 1 1.1 2.4-1.7 3.1-.8 1.4-1.4 .8-2.8-1.7-.2-1.2-5.5-2.8-5-3.1-2.1-1.8-1.2-2.3 .5-.8-2.4-3.7-2.7-5.2-2.6-5.6-1.2-1.3-.9-2-2.1-1.9-2-1.1 .9-1.2-1.3-2.7 .8-1.9 2.2-1.8 .4 1.2-.8 .6 0 1 1.2-.2 1.1 .3 1.2 1.4 1.6-1.1 .5-1.9 1.7-2.4 3.3-1.1 3.1-2.9 .8-1.8-.4-2.1 .8-.3 1.8 1.3 .9 1.3 1.3 .7 1.6 3 2.1 .3 1.5-.7 1 .4 1.7-.2 2.1 1.3-1.8 2.8 .9 .1z',
|
||||
},
|
||||
PG: {
|
||||
name: 'Papua New Guinea',
|
||||
path: 'M891.7 323.3l4.8 1.9 5.1 1.6 1.9 1.4 1.6 1.4 .4 1.6 4.6 1.8 .7 1.4-2.5 .3 .6 1.9 2.4 1.8 1.9 3 1.5-.1-.1 1.2 2.2 .5-.9 .6 3 1.1-.3 .8-1.9 .2-.6-.7-2.4-.3-2.8-.4-2.2-1.8-1.6-1.6-1.4-2.4-3.6-1.2-2.4 .8-1.7 .9 .4 2.1-2.2 .9-1.6-.4-2.8-.2-.1-9.1zM924 326.2l1.1 .9 .3 1.4-.9 .8-.5-1.7-.6-1-1.3-.9-1.6-1.2-2-.8 .8-.7 1.5 .8 .9 .6 1.2 .6zM920.3 332.3l-1.5 .7-1.5 .6-1.4 0-2.3-.8-1.6-.8 .2-.8 2.5 .4 1.5-.3 .5-1.3 .4-.1 .2 1.5 1.6-.2 .8-.9 1.5-1-.3-1.7 1.7 0 .6 .4-.1 1.6-.9 1.7-1.5 .2zM929.9 330.9l.8 .6 1.4 1.8 1.3 .9-.4 .8-.8 .3-1.2-1.1-1.2-1.8-.6-2.1 .4-.2z',
|
||||
},
|
||||
PH: {
|
||||
name: 'Philippines',
|
||||
path: 'M835.6 280.6l-1.4-2.2 2.4 .1 1 1-.8 2.5zM840.5 288.2l.7-.8 .3-1.7 1.6-.2-.5 1.9 2.1-2.7-.3 2.7-1 .9-.9 1.8-.8 .8-1.8-1.9zM851 292.6l.3 1.9 .2 1.5-1 2.6-1-2.9-1.3 1.5 .9 2-.8 1.3-3.2-1.6-.8-2 .8-1.3-1.7-1.4-.9 1.2-1.3-.1-2.1 1.5-.4-.8 1.1-2.3 1.7-.8 1.5-1.1 1 1.3 2.1-.8 .5-1.2 1.9-.1-.1-2.2 2.2 1.4 .3 1.4zM829.2 290.1l-3.7 2.6 1.3-1.9 2.1-1.7 1.6-2 1.5-2.8 .5 2.3-1.9 1.6zM839.8 264.9l-.4 1.1 .9 2.1-.7 2.4-1.6 .9-.5 2.3 .6 2.3 1.5 .3 1.2-.4 3.5 1.6-.3 1.6 .9 .6-.2 1.3-2.2-1.4-1-1.4-.7 1-1.8-1.7-2.5 .4-1.4-.6 .1-1.2 .9-.7-.8-.6-.4 1-1.4-1.7-.4-1.2-.1-2.7 1.1 .9 .3-4.4 .9-2.6 1.7 0 1.7 .8 .9-.8zM839 284.2l-.4-1.3 1.6 .8 1.8 0-.1 1.2-1.2 1.2-1.8 .8-.1-1.3zM848.6 282.1l.8 3.1-2.1-.7 0 .9 .7 1.8-1.3 .6-.1-2-.9-.1-.4-1.7 1.6 .2 0-1.1-1.7-2.1 2.7 0z',
|
||||
},
|
||||
PK: {
|
||||
name: 'Pakistan',
|
||||
path: 'M716.2 213.1l-2.7 2.7-3.1 .5-4.2-.8-1.3 1.3 .9 2.8 1 2.1 2.3 1.5-2.4 1.8 0 2.2-2.7 3-1.7 3.1-2.9 3.2-3.2-.3-3.1 3.2 1.8 1.3 .3 2.3 1.6 1.5 .5 2.5-6.1 0-1.8 2-2.1-.7-.8-2.2-2.1-2.2-5.1 .5-4.6 .1-3.9 .4 1.1-3.4 4-1.6-.3-1.4-1.3-.4-.1-2.7-2.6-1.3-1.1-1.8-1.4-1.6 4.6 1.5 2.8-.4 1.7 .4 .6-.7 1.9 .3 3.6-1.3 .1-2.6 1.5-1.7 2.1 0 .3-.9 2.1-.4 1.1 .3 1-.9-.1-1.8 1.2-1.9 1.7-.8-1-2 2.6 .1 .8-1.2-.2-1.2 1.4-1.3-.3-1.6-.6-1.3 1.6-1.4 3-.6 3.1-.4 1.5-.6 1.6-.4 2 1.5 .8 2.5z',
|
||||
},
|
||||
PL: {
|
||||
name: 'Poland',
|
||||
path: 'M565.2 149.7l.2 1.7 .7 1.4 0 1.5-1.7 .8 .9 1.7 .1 1.7 1.3 3.2-.2 1-1.4 .4-2.5 3.1 .7 1.6-.6-.2-2.7-1.4-2 .5-1.3-.4-1.6 .8-1.4-1.3-1.2 .5-.1-.2-1.3-1.8-2.1-.2-.2-1.2-1.9-.4-.5 1-1.5-.8 .2-1-2.1-.3-1.3-1.2-1.1-2.3 .2-1.3-.7-2-1-1.3 .8-1.1-.7-1.9 1.9-1.1 4.4-1.8 3.5-1.3 2.7 .7 .2 .9 2.7 .1 3.4 .4 5.1-.1 1.5 .5z',
|
||||
},
|
||||
PR: { name: 'Puerto Rico', path: 'M315.9 264l1.4 .3 .5 .5-.7 .8-2.1 0-1.6 .1-.2-1.3 .4-.4z' },
|
||||
PS: { name: 'Palestine', path: 'M598.3 225.6l-1.3 .4 .1-.8 .7-.4-.6-.4 .5-2 1 .4 0 1.9z' },
|
||||
PT: { name: 'Portugal', path: 'M474.9 192.4l1-.8 1.1-.5 .7 1.6 1.7 0 .5-.4 1.6 .1 .8 1.7-1.3 .9-.1 2.6-.4 .5-.1 1.5-1.2 .3 1.1 1.9-.8 2.1 1 1-.4 .9-1 1.2 .2 1-1.1 .9-1.5-.5-1.4 .4 .4-2.5-.3-2-1.2-.3-.7-1.2 .3-2.2 1.1-1.1 .2-1.4 .5-1.9 0-1.4-.6-1.2z' },
|
||||
PY: { name: 'Paraguay', path: 'M338.4 372.8l.8 1.7-.1 3.9 2.9 .6 1.1-.6 1.9 .8 .5 .8 .3 2.7 .3 1.2 1 .1 1.1-.5 1 .6 0 1.6-.4 1.7-.5 1.7-.5 2.7-2.5 2.2-2.2 .5-3.1-.4-2.8-.8 2.7-4.6-.4-1.3-2.9-1.1-3.3-2.2-2.3-.5-5.1-4.7 1.1-3.5 0-1.6 1.4-2.5 4.8-.9 2.6 .1 2.6 1.5z' },
|
||||
QA: { name: 'Qatar', path: 'M641.1 245.9l-.1-2.2 .7-1.5 .8-.4 .8 1 .1 1.7-.7 1.7-.7 .2z' },
|
||||
RO: {
|
||||
name: 'Romania',
|
||||
path: 'M578.4 180.2l1.3 .7 1.3-.6 1.2 .6 .1 .9-1.4 .7-.8-.3-.8 4.1-1.6-.4-2-1.2-3.3 .8-1.4 .9-4.1-.2-2.1-.5-1.1 .2-.8-1.4-.5-.6 .7-.6-.7-.4-.9 .8-1.6-1-.2-1.4-1.7-.8-.3-1.1-1.5-1.4 2.2-.7 1.7-2.3 1.3-2.4 1.7-.8 1.2-.7 1.7 .4 1.8 0 1.3 .9 .9-.6 2.1-.3 .7-.9 1.1 0 .9 .4 .8 1 .9 1.5 1.6 2.1 .1 1.6-.3 1.4z',
|
||||
},
|
||||
RS: {
|
||||
name: 'Serbia',
|
||||
path: 'M552.3 178.8l2.1-1 1.8 .2 1.5 1.4 .3 1.1 1.7 .8 .2 1.4 1.6 1 .9-.8 .7 .4-.7 .6 .5 .6-.6 .8 .2 1.2 1.4 1.5-1.1 1-.5 1.1 .3 .4-.4 .5-1.3 0-1 .2-.1-.2 .4-.4 .3-.8-.4 0-.5-.6-.5-.2-.4-.5-.5-.2-.4-.5-.5 .2-.4 1.1-.6 .2 .2-.3-1.1-.7-.9-.3-.4-.5-.7-.6 .6-.1 .4-1.6-1.3-1.3 .7-1.5-1 0 1.1-1.3-.9-1z',
|
||||
},
|
||||
RU: {
|
||||
name: 'Russian Federation',
|
||||
path: 'M996.5 74.8l3.5-2.2 0 3.5-3 .3zM636.4 177.1l-1.3 2-2.7 .6-2.7 3.5 2.5 3.3-.3 2.2 3.1 4-1.7 1.3-.5 .9-1.2-.3-1.9-2-.8-.1-1.7-.8-.9-1.3-2.6-.7-1.7 .5-.5-.6-3.7-1.6-4.1-.6-2.4-.6-.3 .4-3.6-2.8-3.1-1.3-2.4-2 2-.6 2.3-2.9-1.6-1.4 4.1-1.4 0-.8-2.5 .6 .1-1.6 1.4-1 2.7-.2 .4-1.2-.6-2 1.1-1.8 0-1.1-4.1-1.2-1.6 .1-1.7-1.7-2.2 .5-3.5-1.2 .1-.8-1-1.5-2.2-.2-.3-1.2 .7-.7-1.7-2.1-2.9 .3-.9-.2-.7 .9-1-.2-.7-2.3-.6-1.3 .5-.3 2.2 .1 1.1-.8-.8-1-1.9-.7 .2-.7-1.1-.7-1.8-2.5 .6-1.1-.2-1.8-2.8-.9-1.4 .5-.4-1-2.9-1-.9-2.3-.3-1.9-1.3-1 1.2-1.2-.8-3.8 1.9-2.4-.4-.7 3.2-2.3-2.9-1.9 5.9-5.4 2.6-2.5 1-2.2-4.1-3 1.2-2.9-2.5-3.3 1.8-3.9-3.2-5.3 2.6-3.5-4.3-3.2 .4-3.4 2.3-.5 4.7-1.9 2.9-1.8 4.5 3 7.6 1.2 10.5 5.4 2.2 2.3 .1 3.1-3 2.5-4.6 1.2-12.4-3.5-2 .6 4.5 3.3 .2 2.2 .2 4.6 3.5 1.3 2.2 1.2 .4-2.2-1.7-1.9 1.8-1.7 6.7 2.8 2.3-1.1-1.8-3.3 6.4-4.5 2.6 .2 2.6 1.7 1.6-3.2-2.3-2.8 1.3-2.8-2-3 7.8 1.5 1.6 2.7-3.6 .6 .1 2.6 2.1 1.6 4.3-1 .7-3 5.8-2.3 9.7-4.1 2.1 .2-2.7 3 3.4 .5 2-1.7 5.2-.1 4.1-2 3.2 2.9 3.2-3.2-2.9-2.8 1.4-1.7 8.2 1.5 3.8 1.6 10.1 5.5 1.9-2.5-2.9-2.6 0-1-3.4-.5 .9-2.4-1.5-3.9 0-1.6 5.1-4.6 1.8-4.8 2.1-1 7.3 1.4 .6 2.9-2.6 4.2 1.7 1.6 .9 3.5-.6 6.8 3 3-1.2 3.2-5.4 6.6 3.2 .7 1.1-1.7 3-1.2 .8-2.3 2.4-2.2-1.6-2.8 1.3-3.2-3.1-.4-.7-2.7 2.3-5-3.6-4.1 4.9-3.5-.6-3.7 1.4-.1 1.4 2.9-1.1 5 3 .9-1.3-3.7 4.7-2.1 5.8-.2 5.1 2.9-2.5-4.3-.3-5.7 4.9-1.1 6.7 .3 6-.7-2.3-2.9 3.2-3.6 3.2-.2 5.4-2.7 7.4-.8 .9-1.6 7.3-.5 2.3 1.3 6.2-3 5.1 .1 .8-2.5 2.6-2.5 6.6-2.5 4.7 2-3.8 1.4 6.3 .9 .8 2.9 2.5-1.4 8.1 0 6.3 2.8 2.2 2.2-.7 2.9-3 1.7-7.3 3-2.1 1.6 3.4 .8 4.1 1.4 2.5-1 1.5 3.4 1.2-1.4 4.4-.8 8.9 .9 .7 2.5 11.6 .7 .2-4 5.9 .9 4.4 0 4.5 2.8 1.3 3.4-1.7 2.1 3.5 4.1 4.4 2 2.7-5.3 4.4 2.3 4.8-1.4 5.3 1.6 2.1-1.4 4.5 .7-2-4.8 3.7-2.3 25.1 3.4 2.3 3.1 7.3 3.9 11.2-1 5.6 .8 2.3 2.1-.4 3.7 3.5 1.4 3.7-1 4.9-.2 5.2 1 5.3-.6 4.8 4.4 3.5-1.6-2.3-3.1 1.3-2.2 8.8 1.4 5.8-.3 8 2.3 3.9 2.2 0 18.6-3.6 2-3.6-.3 2.5 2.4 1.7 3.6 1.3 1.2 .3 1.8-.7 1.1-5.2-.9-7.8 3.2-2.5 .5-4.2 3-4 2.6-1.1 1.8-3.9-2.8-7.3 3.2-1.2-1.5-2.7 1.8-3.7-.6-.9 2.7-3.4 4 .1 1.6 3.2 .9-.4 5.8-2.5 .1-1.2 3.3 1.1 1.6-4.8 2-1 4.3-4.1 .9-.9 3.8-4 3.5-1-2.6-1.2-5.4-1.5-8.5 1.3-5.4 2.3-2.4 .2-1.9 4.3-.9 5-5.1 4.7-4.3 5-3.3 2.3-6-3.4 .3-1.7 3.5-7 4.7-2.3-5.2-7.2 1.4-6.9 7.1 2.3 2.5-6.2 1.1-4.3 .4 .2-3-4.3-.6-3.5 2-8.5-.7-9.1 1.2-9 7.9-10.6 9.2 4.3 .5 1.4 2.4 2.7 .8 1.8-1.9 3 .3 4 4.1 .1 3.2-2.2 3.7-.2 4.4-1.2 5.7-4.2 5.2-1 2.4-3.7 4-3.8 3.9-1.8 2.1-3.7 1.9-1.7 .1-1.8-1.7-3.7 2.5-.4 1.1-.4-.6 0-1.7 1.4-.1 .4-4-.7-2.9 2.3-1.2 3.4 .6 1.9-3.4 .9-3.8 1.1-1.3 1.5-3.2-4.6 1.1-2.4 1.4-4.2 0-1.2-3.4-3.3-2.5-4.8-1.2-1-3.5-1-2.3-1-1.6-1.7-3.7-2.5-1.4-4.1-1.1-3.7 .1-3.5 .6-2.3 1.9 1.5 .9 .1 2.1-1.6 1.1-2.5 3.9 .1 1.6-4 2.3-3.3-1.3-3.3 .3-1.5-1.2-1.6-.4-4.1 2.5-3.7 .6-2.5 .9-3.5-.6-2.6 .1-1.7-1.9-2.7-1.7-2.8-.5-3.5 .5-2.6 .6-4-1.5-.5-2.7-3.3-1-2.5-.4-3.1-1.6-2.9 3.9 1.2 2.1-2.7 2.6-4-1-2.8-.1-1.9-1.7-2.9 0-2.4-1.2-4.2 1.8-5.3 3.1-2.9 .6-1.1 .3-1.5-2.2-3.6 .5-1.2-1.6-1.9-.7-1.3-2.1-1.6-.6-4 .9-3.8-2.1-1.5 1.9-6.2-9.4-3.5-3 1-1.2-7 3.6-2.6 .2 .2-2-3.6-1.3-2.9 .9-.9-4-4.9-.8-2.5 1.6-7 1.4-1.3 .9-10.4 1.4-1.3 1.3 2 2.6-2.7 .9 .5 1.1-2.6 1.8 4.5 2.5-.7 1.8-3.9-.2-.8 1.1-3.6-1.9-4.4 .1-2.9 1.5-3.3-1.5-6.1-2.5-4.4 .1-5.7 4-.4 2.6-2.8-2.1-2.2 4 .8 .7-1.6 2.7 2.3 2.4 2.1-.1 1.8 2.4-.3 1.8zM760.5 17.8l6-1.5 5.4 3.3 6.4 6.2-.7 5.6-6.1 .8-7.7-1.8-4.6-2.4-2.1-4.5-3.8-1.3zM785.7 28.9l7 3.5-.8 2.5-15.7 2.3 5.1-8 2.3-.7zM885.6 47.6l7.4 .3 10 3-2.2 4.1-10.2-.1-4.6 1.3-5.5-3.6 1.5-3.9zM911.7 52.1l7 1.5-3.2 2.2-4.5-.5-5.1-2.2 .7-1.8zM888.5 62.9l2.6-2.1 3.5-.5 4 2.1 .3 1.4-4.2 0-5.7-.6zM624.6 20.6l5.4-1.1 4.2-.1 .6 1.7 1.6-1.5 2.6-1.1 4.1 1.4-1.1 1-3.7 .9-2.5 .4-.4 1.1-3.2 1-3-1.5 1.6-2zM563.1 148.1l-5.1 .1-3.4-.4 .6-1.7 3.9-1.3 2.9 .7 1.2 .6-.3 1.1zM648.6 60.9l6.7-4.8-.8-2.5 6.2-3 9.2-3.6 9.3-1.1 4.7-2.1 5.4-.8 2 2.3-1.9 1.8-9.9 2.8-8.4 2.7-8.7 5.2-4.1 5.3-4.4 5.1 .6 4.3 5.3 4.2-1.6 .4-9.1-.6-.7-2.3-5.1-1.4-.4-2.7 2.9-1.2-.1-2.8 5.5-4.6zM897 150.5l.9 3.7 0 3.6 1.1 3.7 2.8 6.5-4.1-1.2-1.7 5.1 2.7 3.6-.1 2.5-2.1-2.1-1.8 2.7-.5-3 .3-3.4-.3-3.8 .6-2.7 .1-4.9-1.6-3.6 .3-5.2 2.5-1.7-1.1-1.8 1.3-.5zM14.1 93.8l-.3 2.9 1.9 1.1-.6-3.3 7.5 .6 5.5 4.4-2.8 2-4.6 .4 0 4.5-1.1 .9-2.6-.2-2.1-1.5-3.7-1.3-.7-2-2.8-.7-3.1 .6-1.6-1.6 .6-1.7-3.3 1.1 1.3 2.1-1.6 1.9 0-18.6 6.8 3.7zM3.6 75.8l-3.6 .3 0-3.5 .4-.2 2.3 0 4 1.5-.2 .7zM592.9 178.5l.7-.8 2 .7 .9 .2 .3 .6 .5 .2 0 .3 1.3 .8 2.9-.2-.6 1.2-3 .6-3.8 2-1.5-.7 .6-1.6-3-1 .4-.7 2.7-1.1z',
|
||||
},
|
||||
RW: { name: 'Rwanda', path: 'M584.5 319.2l1.1 1.6-.2 1.6-.8 .3-1.4-.1-.9 1.5-1.7-.2 .3-1.5 .4-.2 .1-1.7 .8-.7 .6 .2z' },
|
||||
SA: {
|
||||
name: 'Saudi Arabia',
|
||||
path: 'M597.1 232.1l3.1 .5 1.2-.9 .7-1.1 2.1-.5 .4-1 1-.5-2.8-3.1 5.5-1.5 .6-.5 3.3 .9 4.2 2.1 7.8 6.1 5.2 .3 2.4 .3 .7 1.4 2-.1 1.1 2.6 1.3 .7 .5 1.1 1.9 1.2 .2 1.3-.3 .9 .4 1 .8 .9 .3 .9 .4 .8 .9 .5 .7-.2 .6 1.2 .1 .6 1 3 8.4 1.5 .6-.6 1.2 2-1.8 5.8-8.4 2.9-8 1.1-2.6 1.3-1.9 3-1.3 .5-.7-.9-1.1 .1-2.7-.3-.5-.3-3.2 .1-.8 .3-1.1-.8-.7 1.4 .3 1.2-1.3 .9-.3-1.2-.9-.9-.2-1.1-1.4-1-1.5-2.4-.8-2.4-1.9-2-1.2-.4-1.9-2.8-.3-2 .1-1.7-1.6-3.3-1.3-1.1-1.5-.7-.9-1.6 .2-.7-.8-1.5-.8-.7-1.1-2.2-1.7-2.4-1.4-2.1-1.4 0 .4-1.6 .2-1.1z',
|
||||
},
|
||||
SB: { name: 'Solomon Islands', path: 'M950.3 345.3l.8 .9-1.9 0-1.1-1.7 1.7 .7zM949.1 342.8l-.4 .5-2.1-2.4-.5-1.7 .9 0 1 2.2zM946.8 343.6l-1.1 0-1.7-.3-.6-.4 .2-1.1 1.9 .4 .9 .6zM943.4 338.4l.7 .9 .1 .5-2.2-1.2-1.5-1-1-.9 .4-.3 1.3 .7zM936.5 335.6l1.1 .9-.5 .1-1.3-.6-1.1-1.1 .1-.5z' },
|
||||
SD: {
|
||||
name: 'Sudan',
|
||||
path: 'M568.2 293.1l-2.1-1.2-.9-.8-.2-.9 .4-1.1 0-1.2-1.6-1.7-.3-1.2 0-.7-1-.8 0-1.7-.6-1.1-1 .2 .3-1 .7-1.2-.3-1.2 .9-.9-.5-.6 .7-1.8 1.3-2.1 2.4 .2-.2-11.3 .1-1.3 3.1 0 0-5.8 11.2 0 10.8 0 11 0 .9 2.9-.6 .5 .4 3 1 3.4 1.1 .7 1.5 1.1-1.4 1.6-2.1 .5-.8 .9-.3 1.9-1.2 4.2 .3 1.1-.4 2.4-1.2 2.8-1.7 1.4-1.1 2.2-.3 1.1-1.3 .8-.9 2.9 .1 2.6-.1-2.2-.3-.1 0-1.4-.3-.9-1.5-1.2-.3-2 .3-2.1-1.2-.2-.2 .7-1.7 .1 .7 .8 .2 1.7-1.5 1.6-1.4 2-1.4 .3-2.4-1.6-1 .5-.3 .9-1.4 .5-.1 .6-2.8 0-.4-.6-2-.1-1 .5-.8-.3-1.4-1.6-.5-.8-2 .4-.7 1.3-.7 2.5-1 .5-.8 .3z',
|
||||
},
|
||||
SE: {
|
||||
name: 'Sweden',
|
||||
path: 'M530.6 130.3l1.3-2.4 2.3-2.8 .9-5-1.8-2.1-.2-5.8 1.8-4.1 2.8 .1 1-1.8-1-1.5 4.3-6.4 2.7-5.2 1.9-3.4 2.6 .1 .8-2.7 5.2 .8 .4-3.2 1.7-.2 3.8 2.4 4.3 3.2 .1 7.3 .9 1.7-4.8 1.3-2.7 3.2 .5 2.8-4.5 3.5-5.3 3.8-2 6 1.9 3 2.7 2.3-2.6 4.7-2.9 .9-1 6.8-1.6 3.7-3.4-.4-1.5 3.1-3.2 .2-.9-3.7-2.4-4.5z',
|
||||
},
|
||||
SI: { name: 'Slovenia', path: 'M538.4 176.7l2.2 .2 1.4-.7 2.5-.1 .5-.6 .5 0 .5 1.2-2.2 .9-.3 1.4-.9 .4 0 .9-1.1 0-1-.6-.5 .6-1.9-.1 .6-.3-.6-1.5z' },
|
||||
SK: { name: 'Slovakia', path: 'M562.7 167.6l-.8 .9-.6 1.4-.5 .4-3-1.1-.9 .2-.7 .9-1.3 .4-.3-.2-1.3 .5-1.1 .1-.3 .8-2.3 .4-1-.4-1.4-.9-.3-1.2 .2-.5 .4-.8 1.2 .1 1-.4 .1-.3 .5-.2 .2-.8 .6-.2 .4-.6 .9 0 .1 .2 1.2-.5 1.4 1.3 1.6-.8 1.3 .4 2-.5z' },
|
||||
SL: { name: 'Sierra Leone', path: 'M463.2 291.2l1.5-1.2 .3-.8 .5-.6 .7 0 .7-.6 2.2 0 .8 1 .6 1.2-.1 .8 .4 .8 0 1 .8-.2-1.3 1.3-1.3 1.6-.1 .8-.7 .9-.7-.2-2-1.2-1.5-1.5-.5-1z' },
|
||||
SN: { name: 'Senegal', path: 'M453.6 278l-1.2-2.2-1.4-1 1.3-.5 1.3-2 .7-1.5 .9-.9 1.4 .3 1.4-.7 1.5 0 1.3 .9 1.9 .7 1.7 2.1 1.8 2 .1 1.7 .6 1.6 1 .8 .2 1.1-.1 .9-.4 .2-1.5-.3-.2 .3-.6 .1-2-.7-1.4 0-5.1-.1-.7 .3-1-.1-1.4 .5-.5-2.2 2.5 .1 .7-.4 .5-.1 1-.6 1.2 .6 1.2 0 1.2-.6-.5-.8-.9 .5-.9-.1-1.1-.7-.9 .1-.6 .7z' },
|
||||
SO: { name: 'Somalia', path: 'M615.5 320.7l-1.6-2.3-.1-10.1 2.5-3.2 .7-.8 1.8-.1 2.5-1.9 3.6-.2 7.8-8.3 2-2.4 1.2-1.7 0-1.5 0-2.8 .1-1.1 0-.1 .9 0 1.2-.5 1.5-.2 1.3-1 1.1 0 0 .8-.2 1.6 0 1.5-.6 1-.8 3-1.3 3.1-1.7 3.6-2.4 4.1-2.4 3.1-3.3 3.8-2.7 2.3-4.2 2.7-2.6 2.1-3 3.4-.7 1.5z' },
|
||||
SR: { name: 'Suriname', path: 'M348.5 309.6l-1.5-.6-1.4 .3-1.1-.2-.3 .8 .5 .5-.2 .6-1.6-.2-1.7-2.5-.3-1.5-.9 0-1.2-2 .5-1.5-.2-.6 1.7-.8 .5-2.5 3.3 .6 .3-.5 2.2-.2 3 .7-1.4 2.4 .2 1.9 1.1 1.7-.5 1.2-.2 1.2z' },
|
||||
SS: {
|
||||
name: 'South Sudan',
|
||||
path: 'M585.6 306.3l-2.4-1.9-.7-1.2-1.5 .6-1.3-.1-.7 .4-1.3-.3-1.7-2.3-.4-.9-2.1-1.1-.7-1.7-1.1-1.2-1.9-1.4 0-.9-1.6-1.2-1.8-1.1 .8-.3 1-.5 .7-2.5 .7-1.3 2-.4 .5 .8 1.4 1.6 .8 .3 1-.5 2 .1 .4 .6 2.8 0 .1-.6 1.4-.5 .3-.9 1-.5 2.4 1.6 1.4-.3 1.4-2 1.5-1.6-.2-1.7-.7-.8 1.7-.1 .2-.7 1.2 .2-.3 2.1 .3 2 1.5 1.2 .3 .9 0 1.4 .3 .1 .1 2.2-.4 .8-1.5 .1-1 1.6 1.7 .2 1.5 1.3 .4 1.1 1.3 .7 1.7 3-1.9 1.9-1.7 1.6-1.7 1.3-2 0-2.2 .7-1.8-.7z',
|
||||
},
|
||||
SV: { name: 'El Salvador', path: 'M251.8 275.7l.8 .2 .6 .6 .9 .5 .1 .3 1.2-.3 .5 .2 .4 .3-.2 1.1-.3 .7-1.6 0-1-.3-1.1-.6-1.6-.1-.8-.7 .1-.4 1-.7 .5-.3-.2-.3z' },
|
||||
SY: { name: 'Syria', path: 'M599.2 221.8l.3-.4 0-1.3 .7-1.7 1.5-1.2-.5-1.2-1.2-.2-.3-2.4 .7-1.3 .8-.7 .7-.7 .2-1.8 .9 .7 3-.9 1.5 .6 2.3 0 3.2-1.2 1.5 0 3.1-.5-1.4 2-1.5 .8 .3 2.3-1.1 3.8-6.1 3.3-5.5 3.3z' },
|
||||
SZ: { name: 'Kingdom of eSwatini', path: 'M589.1 392.1l-.6 1.3-1.6 .3-1.7-1.6 0-1 .8-1.1 .2-.9 .8-.2 1.4 .5 .4 1.4z' },
|
||||
TD: {
|
||||
name: 'Chad',
|
||||
path: 'M566.2 261l.2 11.3-2.4-.2-1.3 2.1-.7 1.8 .5 .6-.9 .9 .3 1.2-.7 1.2-.3 1 1-.2 .6 1.1 0 1.7 1 .8 0 .7-1.7 .4-1.5 1.2-2 3-2.6 1.3-2.7-.1-.7 .2 .2 1-1.4 1-1.2 1.1-3.5 1-.7-.6-.4 0-.6 .7-2.3 .2 .5-.8-.9-1.9-.4-1.2-1.2-.4-1.6-1.7 .6-1.3 1.2 .3 .8-.2 1.6 0-1.5-2.5 .1-1.9-.2-1.9-1.1-1.8 .2-1.3-1.7-.1 0-1.8-1.2-1 1.2-3.8 3.6-2.7 .1-3.7 1.1-5.8 .6-1.3-1.2-1 0-.9-1.1-.7-.6-4.6 2.8-1.6 11 5.6z',
|
||||
},
|
||||
TF: { name: 'French Southern and Antarctic Lands', path: 'M691.5 462.9l1.8 1.1 2.6 .4 .1 .7-.8 1.7-4.2 .2-.1-1.9 .4-1.5z' },
|
||||
TG: { name: 'Togo', path: 'M502.5 285.4l-.4 1.4 .9 .9 1 1 .1 1.3 .5 .6-.1 6.4 .7 2-2.3 .6-.6-1-.7-1.8-.2-1.4 .6-2.5-.7-1-.3-2.2 0-2.1-1.1-1.4 .2-.9z' },
|
||||
TH: {
|
||||
name: 'Thailand',
|
||||
path: 'M792.3 276.1l-2.6-.4-3.6 .6-1.8 2.3 .7 3.4-2.5-1.3-2.4 .1 .4-2.2-2.5 0-.2 3.1-1.5 4.1-.9 2.5 .2 2 1.8 .1 1.2 2.5 .5 2.5 1.5 1.6 1.7 .3 1.4 1.4-.9 1.2-1.8 .3-.2-1.4-2.3-1.2-.5 .5-1.1-1.1-.5-1.4-1.4-1.6-1.4-1.3-.4 1.6-.6-1.5 .3-1.8 .9-2.6 1.3-2.9 1.5-2.6-1.1-2.6 .1-1.3-.3-1.6-1.9-2.3-.6-1.4 .9-.5 1-2.5-1.1-1.9-1.8-2.1-1.3-2.5 1.2-.5 1.2-3.1 2-.1 1.6-1.3 1.6-.7 1.2 .9 .2 1.8 1.8 .1-.6 3 0 2.6 2.9-1.7 .9 .5 1.6-.1 .6-1 2.1 .2 2.1 2.3 .2 2.9 2.2 2.4-.1 2.4z',
|
||||
},
|
||||
TJ: { name: 'Tajikistan', path: 'M688.4 207.9l1.6-3.3-.6-2.4-2.1-.7 .8-1.5 2.3 .2 1.3-1.8 .9-2.1 3.7-.8-.6 1.5 .4 .9 1.2 0-1.1 1-3-.6-.2 1.9 3-.3 3.4 1.1 5.3-.5 .7 3 .9-.3 1.7 .7-.1 1.3 .4 1.8-2.9 0-1.9-.2-1.7 1.4-1.3 .3-.9 .7-1.1-1.1 .2-2.6-.8-.2 .3-1-1.5-.7-1.2 1.1-.3 1.3-.4 .5-1.7-.1-.9 1.5-.9-.6-2 1z' },
|
||||
TL: { name: 'Timor-Leste', path: 'M847.1 340.8l.4-.7 2.4-.6 1.9-.1 .9-.3 1 .3-1 .8-2.9 1.2-2.3 .8-.1-.8z' },
|
||||
TM: {
|
||||
name: 'Turkmenistan',
|
||||
path: 'M645.8 192.8l1.3-1.1 3.1-.7 1.9 .9 1.9 2.6 1.5-.2 3.1 0-.5-1.7 2.4-1.1 2.4-2 3.7 1.8 .3 2.7 1.1 .6 3-.1 .9 .6 1.4 3.4 3.1 2.2 1.9 1.6 2.9 1.6 3.7 1.3-.1 2-.9-.1-1.3-.9-.4 1.2-2.3 .6-.6 2.5-1.6 1-2.2 .5-.5 1.4-2.1 .4-2.9-1.2-.2-2.6-2.1-.1-3.2-2.9-2.2-.3-3-1.6-2-.3-1.2 .6-1.9-.1-2 1.8-2.4 .6-.5-2.2 .4-3.4-2.2-1.1 .7-2.3-1.8-.2 .6-2.7 2.6 .8 2.4-1.1-2-2-.8-1.9-2.2 .9-.3 2.4z',
|
||||
},
|
||||
TN: { name: 'Tunisia', path: 'M526.3 229.2l-1.1-5.5-1.8-1.2 0-.8-2.3-1.8-.2-2.4 1.7-1.7 .7-2.6-.5-3 .6-1.7 3-1.3 2 .4-.1 1.6 2.3-1.1 .2 .6-1.4 1.5 0 1.5 1 .8-.4 2.7-1.8 1.6 .5 1.7 1.5 .1 .7 1.4 1 .5-.1 2.4-1.4 .9-.9 1-1.9 1.2 .3 1.2-.2 1.3z' },
|
||||
TR: {
|
||||
name: 'Turkey',
|
||||
path: 'M624.4 207.8l-1.4 .6-.9-.9-3.3-.4-1.2 .5-3.1 .5-1.5 0-3.2 1.2-2.3 0-1.5-.6-3 .9-.9-.7-.2 1.8-.7 .7-.8 .7-1-1.4 1-1.2-1.6 .2-2.4-.7-1.9 1.8-4.2 .4-2.2-1.7-3-.1-.7 1.3-1.9 .4-2.7-1.7-3 0-1.7-3.1-2-1.8 1.4-2.5-1.8-1.6 3.1-3.1 4.3-.2 1.1-2.5 5.3 .5 3.4-2.2 3.2-.9 4.6-.1 4.8 2.4 4 1.2 3.3-.5 2.3 .3 3.3-1.7 3-.2 2.7 1.7 .4 1.1-.2 1.6 2 .8 1.1 1-1.9 .9 .9 3.7-.6 1zM572.5 192.6l2.9-1 2.4 .4 .3 1.3 2.4 1.1-.5 .8-3.3 .2-1.2 1-2.3 1.8-.9-1.6 .1-.7 .6-.3 .9-2.1z',
|
||||
},
|
||||
TT: { name: 'Trinidad and Tobago', path: 'M328.7 286l1.6-.3 .5 .1-.1 2.1-2.3 .3-.5-.3 .8-.8z' },
|
||||
TW: { name: 'Taiwan', path: 'M838.3 246.9l-1.7 4.7-1.2 2.4-1.5-2.4-.3-2.2 1.7-2.9 2.2-2.2 1.3 .8z' },
|
||||
TZ: {
|
||||
name: 'Tanzania',
|
||||
path: 'M594.2 318.7l.4 .3 10.1 5.6 .2 1.7 4 2.7-1.3 3.5 .2 1.6 1.8 1 0 .7-.7 1.7 .1 .8-.1 1.4 .9 1.7 1.2 2.8 1 .6-2.2 1.6-3.1 1.1-1.6 0-1 .8-1.9 .1-.8 .3-3.3-.8-2.1 .3-.8-3.8-.9-1.4-.6-.7-2.7-.5-1.6-.9-1.7-.5-1.2-.4-1.1-.7-1.5-3.6-1.6-1.5-.6-1.6 .3-1.5-.5-2.6 1.1-.1 1.1-1 1-1.4 .7-.6 0-.9-.6-.7-.2-1.1 .8-.3 .2-1.6-1.1-1.6 1-.3 3 0z',
|
||||
},
|
||||
UA: {
|
||||
name: 'Ukraine',
|
||||
path: 'M588.3 156.5l1 .2 .7-.9 .9 .2 2.9-.3 1.7 2.1-.7 .7 .3 1.2 2.2 .2 1 1.5-.1 .8 3.5 1.2 2.2-.5 1.7 1.7 1.6-.1 4.1 1.2 0 1.1-1.1 1.8 .6 2-.4 1.2-2.7 .2-1.4 1-.1 1.6-2.2 .3-1.9 1.1-2.6 .2-2.4 1.3 .2 1.9-.5-.2-.3-.6-.9-.2-2-.7-.7 .8-.4-.3-4.3-.9-.2-1.3-2.6 .4-1 1.9-2.2 2.6-1.2-.6-1.3 .6-1.3-.7 .7-.4 .5-1.1 .8-1.2-.2-.6 .6-.3 .2 .5 1.7 .1 .7-.2-.5-.4 .2-.5-1-.9-.4-1.5-1-.5 .2-1.2-1.3-1-1.1-.1-2-1.1-1.9 .3-.7 .5-1.1 0-.7 .9-2.1 .3-.9 .6-1.3-.9-1.8 0-1.7-.4-1.2 .7-.2-.9-1.6-1 .6-1.4 .8-.9 .6 .2-.7-1.6 2.5-3.1 1.4-.4 .2-1-1.3-3.2 1.3-.2 1.5-1 2.2 0 2.8 .2 3.1 .9 2.1 .1 1.1 .5 1-.6 .8 .9 2.5-.2 1.1 .3 .2-1.8 .8-.8z',
|
||||
},
|
||||
UG: { name: 'Uganda', path: 'M594.2 318.7l-5.7 .2-3 0-1 .3-1.7 .8-.6-.2 0-2.1 .6-1.1 .2-2.2 .6-1.3 1-1.5 1.1-.7 .9-1-1.1-.4 .1-3.2 1.2-.8 1.8 .7 2.2-.7 2 0 1.7-1.3 1.3 2 .3 1.4 1.2 3.1-1 2.1-1.4 1.8-.8 1.1z' },
|
||||
US: {
|
||||
name: 'United States',
|
||||
path: 'M158.8 167.9l7.9 0 8.2 0 2.7 0 8.5 0 8.2 0 8.3 0 8.4 0 9.4 0 9.5 0 5.8 0 0-1.4 .9 0 .5 1.9 .9 .6 1.9 .3 2.9 .5 2.6 1.1 2.3-.4 3.4 .9 .9 0 2.5-1 2.6 1.2 2.7 1.4 2.3 1.2 2.1 1.1 .3 .9 .7 .4-.2 .3 .7 .1 .6-.3 .1 .8 .6 .5 .7 0 .4 .5-.3 .6 2.9 1.6 .6 3.1 .5 3-.8 2-1.3 1.8-.6 1.2-.1 .3 .4 .5 .9 .5 .7 0 3.2-1.8 2.9-.5 3.6-1.6 .1-.4-.3-1-.4-.7 1.2-.5 2.8 0 2.5 0 .9-1.4 .3-.2 3-2.5 1.2-.6 4.3 0 5.1 0 .3-.9 .8-.2 1.2-.5 1-1.6 .9-2.7 2.1-2.6 .9 .9 1.9-.6 1.2 1 0 4.8 1.8 1.9 .5 1.1-3 1.7-2.8 1.2-3 1-1.4 2-.5 .7 0 1.8 .9 1.8 1.1 .1-.3-1.2 .9 .7-.2 1-1.9 .5-1.4-.1-2 .6-1.2 .2-1.6 .1-2.3 1 4-.6 .9 .6-3.9 1-1.8 0 .1-.4-.9 .9 .8 .2-.6 2.3-2 2.5-.2-.8-.6-.2-.9-.8 .6 1.7 .7 .6 0 1.2-.9 1.3-1.5 2.6-.3-.2 .9-2.1-1.5-1.3-.3-2.7-.5 1.4 .6 2.1-1.9-.5 2 1 .1 3.1 .8 .2 .3 1.1 .3 3.2-1.7 2.3-2.9 .9-1.8 1.9-1.4 .2-1.4 1.1-.4 1.1-3.1 2-1.5 1.4-1.3 1.9-.5 2.1 .5 2.1 1 2.6 1.2 2.2 0 1.3 1.3 3.5-.1 2-.1 1.1-.7 1.8-.8 .4-1.4-.4-.4-1.3-1.1-.6-1.4-2.6-1.3-2.3-.5-1.2 .6-2-.8-1.6-2.1-2.6-1.1-.4-2.8 1.4-.5-.2-1.4-1.4-1.7-.8-3.1 .4-2.5-.3-2.1 .2-1.2 .5 .5 .8 0 1.2 .6 .6-.6 .4-1-.4-1 .5-2-.1-2.1-1.6-2.4 .4-2.1-.7-1.7 .2-2.3 .7-2.6 2.3-2.7 1.3-1.5 1.4-.7 1.4 0 2 .1 1.5 .6 1-1.1 .1-2-.7-2.2-.9-.7-1.4-.6-2.1-1.7-1.7-.9-1.8-1.4-2.1-2-1.2-2.3 .1-1.7 2.4-2.3-.9-1.5-.9-.6-1.7-1-1.6-1.6-1.4-1.4-.9-1.1-1.1-4.8 0 0 1.3-2.2 0-5.5 0-6.3-2.2-4.2-1.5 .2-.6-3.5 .3-3.2 .3-.4-1.6-1.8-1.8-1.3-.4-.3-.9-1.6-.1-1-.9-2.6-.3-.7-.5-.3-1.7-2.7-3.2-2.3-4.4 .1-.8-1.2-1-2.2-2.7-.4-2.7-1.5-1.8 .7-2.7-.1-2.9-.9-2.5 1.1-3.2 .3-3.1 .3-3.1-.5-4.7-.8-3-.9-1.6 .4-.7 4 1.2 1.5 3.3 .7-.9-.5-2.9zM68.3 259.5l.5 .3 .5 .4 .7 1-.1 .1-1.1 .6-.9 .5-.4 .5-.7-.4 .1-.8-.4-1.1 .1-.3 .5-.5-.2-.6 .2-.2 .2 0zM66.7 257.5l-.3 .4-.9 .2-.5-.6-.3-.3 0-.1 .3-.3 1 .3zM64.6 256.3l-.1 .4-1.5-.1 .2-.4zM61 254.8l.3 .2 .8 .9-.2 .2-.2-.1-.9-.1-.4-.6-.1-.1zM57.3 253.3l.1 .7-.4 .3-.9-.5 .2-.3 .4-.2zM37.6 124l2.2 .3 .3 1.6-1.7 .7-1.9-.8-1.7-1.1zM74.4 133.9l1.8 .2 1.2 1.3-2.4 1.9-2.8 1.5-1.4-1-.4-1.9 2.5-1.4zM108.4 81.7l0 17.7-.1 24.9 2.8 .1 2.7 1.2 1.9 1.8 2.5 2.7 2.7-2.3 2.8-1.4 1.5 2.2 1.8 1.6 2.6 1.9 1.7 2.9 2.8 4.5 4.8 2.5 0 2.5-1.5 1.8-1.5-1.4-2.5-1.3-.8-3.4-3.5-3.2-1.5-3.8-2.7-.2-4.4-.1-3.3-1.2-5.7-4.2-2.7-.8-4.8-1.5-3.9 .4-5.4-2-3.4-1.7-3 .8 .5 2.9-1.5 .3-3.2 .9-2.5 1.4-3 .8-.4-2.4 1.2-4.1 3-1.3-.8-1-3.5 2.3-1.9 2.8-4 2.9 2 2-2.6 2.9-3 1.7-2.8 1.2-.7 1.8-4.3 2.1-.9 1.8-3.2 1.7-1.9-.3-2.6 1.1-2.9 1.3-2.3 1.3-4.7 1.1-.5-.7 3.1-1.8 2.7-1.2 2.9-2.1 3.5-.5 1.4-1.6 3.8-2.3 .6-.8 2.1-1.4 .5-3.1 1.4-2.4-3.2 1.2-.9-.7-1.5 1.5-1.8-2-.8 1.4-1-2-2.8 1.6-1.7 0-.3-2.4 .5-1.5-1.7-1.5-3.7 .8-2.3-2-1.9-1 0-2.3-2.1-1.8 1-2.5 2.3-2.4 1-2.2 2.2-.4 1.9 .7 2.3-2.1 2 .4 2.1-1.4-.5-2-1.6-.8 2.1-1.7-1.7 0-3 1-.8 1-2.2-1-3.9 .5-4.1-1.1-1.2-1.8-3.5-2.6 3.9-1.9 6.2-2.3 2.3 0-.4 2.3 5.9-.2-2.3-2.8-3.4-1.8-2-2.4-2.6-2-3.8-1.5 1.5-2.5 4.9-.2 3.6-2.2 .6-2.4 2.9-2.4 2.7-.6 5.2-2.2 2.6 .3 4.3-2.7 4.2 1.1 2 2.3 1.2-1 4.7 .3-.2 1.2 4.3 .8 2.8-.5 5.9 1.6 5.3 .5 2.1 .6 3.7-.8 4.3 1.5zM23 109.4l1.7 .8 1.7-.4 2.3 1.1 2.7 .6-.2 .5-2.1 .9-2.1-.9-1.1-.8-2.4 .2-.7-.4z',
|
||||
},
|
||||
UY: { name: 'Uruguay', path: 'M339.9 402.6l1.8-.3 2.8 2.3 1.1-.1 2.8 2 2.2 1.7 1.6 2.1-1.2 1.5 .7 1.7-1.2 2-3.1 1.7-2.1-.6-1.5 .3-2.5-1.3-1.9 .1-1.7-1.7 .2-2.1 .6-.6 0-3.1 .7-3.2z' },
|
||||
UZ: {
|
||||
name: 'Uzbekistan',
|
||||
path: 'M655.5 194.3l-.1-12.4 7.1-2 .5 .3 4.3 2.4 2.3 1.3 2.7 3.1 3.2-.5 4.8-.3 3.3 2.5-.2 3.4 1.4 0 .5 2.7 3.5 .1 .8 1.6 1 0 1.3-2.4 3.6-2.3 1.6-.6 .8 .3-2.3 2.1 2.1 1.3 1.9-.8 3.3 1.7-3.5 2.4-2.1-.3-1.2 0-.4-.9 .6-1.5-3.7 .8-.9 2.1-1.3 1.8-2.3-.2-.8 1.5 2.1 .7 .6 2.4-1.6 3.3-2.1-.7-1.5 0 .1-2-3.7-1.3-2.9-1.6-1.9-1.6-3.1-2.2-1.4-3.4-.9-.6-3 .1-1.1-.6-.3-2.7-3.7-1.8-2.4 2-2.4 1.1 .5 1.7z',
|
||||
},
|
||||
VE: {
|
||||
name: 'Venezuela',
|
||||
path: 'M331.3 301.6l.4 .8-1.1 1-3.1 1.1-2 .4-.8 .7-2.2-.7-2-.4-.5 .3 1.2 .7-.1 1.9 .4 1.7 2.3 .2 .2 .6-2 .8-.3 1.2-1.2 .4-2 .7-.6 .8-2.1 .2-1.6-1.4-.8-2.8-.8-1-1-.6 1.4-1.4 0-.6-.8-.8-.6-1.9 .2-2 .6-.9 .5-1.5-.9-.5-1.6 .3-2-.1-1.1 .3-2-2.4-1.6-.4-3.6 .3-.7-1-.6-.2-.1-.6 .3-1-.2-1.2-.6-.6-.4-1.3-1.4-.2 .7-1.6 .4-2 .8-1 1.1-.8 .7-1.4 1.8-.5-.1 .6-1.7 .4 1 1.2-.1 1.5-1.2 1.6 1 2.3 1.2-.2 .7-2-.9-1-.1-2.2 3.4-1.1-.4-1.3 1-.9 1 2 2 0 1.8 1.6 .1 .9 2.5 0 2.9-.3 1.6 1.3 2.1 .4 1.6-.9 0-.7 3.5-.2 3.3 0-2.4 .8 1 1.3 2.2 .2 2.1 1.4 .5 2.2 1.4 0 1.1 .6-2.2 1.7-.2 1 .9 1-.7 .6-1.7 .4 .1 1.3-.8 .8z',
|
||||
},
|
||||
VN: {
|
||||
name: 'Vietnam',
|
||||
path: 'M789.8 286.8l2.4-1.1 2.9-.2-1.2-1.7 4.7-2.2 .3-3.4-.6-1.9 .5-2.8-.7-2-2.1-2-1.8-2.5-2.3-3.4-3.3-1.7 .8-1.1 1.8-.7-1.1-2.5-3.4-.1-1.3-2.6-1.6-2.3 1.5-.7 2.2 0 2.7-.3 2.4-1.6 1.3 1.1 2.6 .5-.5 1.7 1.3 1.2 2.8 .7-3.7 2.5-2.3 2.8-.6 2 2.1 3 2.6 3.7 2.5 1.8 1.7 2.3 1.3 5.2-.4 5-2.3 1.8-3.2 1.8-2.2 2.4-3.5 2.6-1-1.8 .8-1.9z',
|
||||
},
|
||||
VU: { name: 'Vanuatu', path: 'M964.5 360.5l1.7 1.7-.9 .4-.9-1.3zM963.3 359.9l-.4-.8 0-2.1 1.3 .8 .4 2.3-.7-.3z' },
|
||||
XC: { name: 'Northern Cyprus', path: 'M590.9 214.3l.2-.1 .4-.7 2 0 2.5-.9-1.8 1.3 .2 .6-.3-.1-.6 .2-.4 0-.1 .1-.1-.3-.2-.2-.5 0-.8 .2z' },
|
||||
XK: { name: 'Kosovo', path: 'M557.2 192.5l-.2-1.2-.7-.3-.5-.9 .5-.8 .6-.2 .4-1.1 .5-.2 .4 .5 .5 .2 .4 .5 .5 .2 .5 .6 .4 0-.3 .8-.4 .4 .1 .2-.6 .2-1.6 .5-.2 .7z' },
|
||||
XS: { name: 'Somaliland', path: 'M636 284.2l0 .1-.1 1.1 0 2.8 0 1.5-1.2 1.7-2 2.4-2.3 0-9.1-3.3-1-1-1.1-1.4-1-1.5 .6-1 1-1.5 1 .5 .5 1.1 1.2 1.2 1.4 0 2.6-.7 3.1-.3 2.4-.9 1.4-.2 1-.5z' },
|
||||
YE: {
|
||||
name: 'Yemen',
|
||||
path: 'M644.4 262.6l2.2 4.8 .9 2-2 .7-.5 1.3-.1 1-2.8 1.2-4.4 1.3-2.5 2-1.2 .1-.8-.1-1.7 1.2-1.7 .5-2.4 .1-.7 .2-.6 .8-.7 .2-.4 .7-1.4-.1-.9 .4-1.9-.1-.7-1.7 0-1.5-.4-.9-.6-2.1-.8-1.1 .6-.2-.3-1.3 .4-.5-.2-1.3 1.3-.9-.3-1.2 .7-1.4 1.1 .8 .8-.3 3.2-.1 .5 .3 2.7 .3 1.1-.1 .7 .9 1.3-.5 1.9-3 2.6-1.3z',
|
||||
},
|
||||
ZA: {
|
||||
name: 'South Africa',
|
||||
path: 'M545.4 397.6l1.3-1.5 1.1 .8 .5 1.3 1.2 .3 1.8 .5 1.5-.2 2.5-1.5 0-11 .7 .4 1.7 2.8-.3 1.8 .6 1.1 2-.3 1.4-1.4 1.3-.9 .7-1.4 1.4-.7 1.1 .4 1.4 .8 2.2 .2 1.8-.7 .3-.9 .5-1.5 1.5-.2 .8-1.1 .9-2 2.5-2.1 4-2.2 1.1 0 1.3 .5 1-.3 1.4 .3 1.4 4.1 .7 2.1-.5 3.3 .2 1-1.4-.5-.8 .2-.2 .9-.8 1.1 0 1 1.7 1.6 1.6-.3 .6-1.3 2.1 0-.7 2.2-.3 2.5-.7 1.3-1.9 1.6-.6 .4-1.2 1.5-.7 1.6-1.6 2.2-3.2 3.2-1.9 1.8-2.1 1.4-2.9 1.2-1.4 .2-.4 .9-1.7-.5-1.4 .6-3-.6-1.6 .4-1.2-.2-2.9 1.3-2.3 .4-1.7 1.2-1.3 .1-1.2-1.1-.9-.1-1.2-1.4-.2 .5-.3-.9 0-1.8-.9-2.1 .9-.5-.1-2.4-1.8-2.9-1.4-2.5 0-.1zM580.5 398.8l-1.2-1-1.3 .6-1.5 1.2-1.5 1.9 2.1 2.4 1-.3 .5-1 1.5-.5 .5-1 .9-1.4z',
|
||||
},
|
||||
ZM: {
|
||||
name: 'Zambia',
|
||||
path: 'M585.4 339.3l1.1 .7 1.2 .4 1.7 .5 1.6 .9 1.3 1.2 .7 2.4-.5 .7-.5 2.3 .5 2.4-.9 .9-.8 2.7 1.5 .7-8.5 2.3 .3 2.1-2.1 .3-1.6 1.2-.3 1-1 .2-2.4 2.3-1.6 1.9-.9 .1-.9-.4-3.1-.3-.5-.2 0-.2-1.1-.7-1.8-.1-2.3 .6-1.8-1.8-1.9-2.3 .1-9 5.8 0-.2-1 .4-1-.5-1.4 .3-1.3-.3-.9 1 .1 .1 .9 1.3-.1 1.8 .2 .9 1.3 2.3 .4 1.7-.9 .6 1.5 2.1 .4 1 1.2 1.2 1.6 2.1 0-.2-3.1-.8 .5-1.9-1.1-.8-.5 .4-2.8 .4-3.3-.6-1.2 .8-1.8 .8-.4 3.7-.4z',
|
||||
},
|
||||
ZW: { name: 'Zimbabwe', path: 'M586.6 378.9l-1.4-.3-1 .3-1.3-.5-1.1 0-1.8-1.3-2.2-.5-.8-1.8 0-1-1.2-.3-3.1-3.2-.9-1.7-.6-.5-1-2.3 3.1 .3 .9 .4 .9-.1 1.6-1.9 2.4-2.3 1-.2 .3-1 1.6-1.2 2.1-.3 .2 1 2.3 0 1.3 .6 .6 .7 1.3 .2 1.4 .9 0 3.6-.5 2-.1 2.2 .4 .8-.3 1.7-.4 .3-.7 2.1z' },
|
||||
},
|
||||
}
|
||||
Vendored
+2
@@ -12,5 +12,7 @@ declare global {
|
||||
IMask?: new (element: HTMLElement, options: { mask: unknown; lazy?: boolean } & Record<string, unknown>) => import('./input-mask').IMaskInstance
|
||||
Sortable?: new (element: HTMLElement, options?: Record<string, unknown>) => import('./sortable').SortableInstance
|
||||
jQuery?: JQueryStaticLike
|
||||
// Filled by the map files of the VectorMap plugin (`dist/js/maps/*.js`)
|
||||
tablerVectorMaps?: Record<string, import('./vector-map').VectorMapData>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Tabler vector-map.ts
|
||||
* Licensed under MIT (https://github.com/tabler/tabler/blob/dev/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './bootstrap/base-component'
|
||||
import EventHandler from './bootstrap/dom/event-handler'
|
||||
import { initAll } from './bootstrap/util/component-functions'
|
||||
import type { ComponentConfig as BaseConfig, ElementSelector } from './bootstrap/types'
|
||||
|
||||
export type VectorMapRegion = {
|
||||
name: string
|
||||
/** SVG path data, already projected into the viewBox of the map */
|
||||
path: string
|
||||
}
|
||||
|
||||
export type VectorMapData = {
|
||||
width: number
|
||||
height: number
|
||||
/** How the shapes were projected, to place a point by its coordinates */
|
||||
projection: { type: 'miller'; scale: number; translate: number[] }
|
||||
/** Regions by their code, for example ISO 3166-1 alpha-2 for countries */
|
||||
regions: Record<string, VectorMapRegion>
|
||||
}
|
||||
|
||||
export type VectorMapMarker = {
|
||||
/** Shown in the tooltip, and what a line refers to */
|
||||
name?: string
|
||||
lat: number
|
||||
lng: number
|
||||
}
|
||||
|
||||
export type VectorMapLine = {
|
||||
/** Names of the two markers the line joins */
|
||||
from: string
|
||||
to: string
|
||||
}
|
||||
|
||||
/** What the pointer is over when a tooltip is about to show */
|
||||
export type VectorMapTooltipItem = { type: 'region'; code: string; name: string; value?: number } | { type: 'marker'; name: string }
|
||||
|
||||
type VectorMapValues = Record<string, number>
|
||||
type VectorMapTooltip = boolean | ((item: VectorMapTooltipItem) => string)
|
||||
type Point = [number, number]
|
||||
// The part of the map in sight: its top left corner and how many times it is magnified
|
||||
type View = { x: number; y: number; scale: number }
|
||||
|
||||
type ComponentConfig = {
|
||||
map: string | VectorMapData
|
||||
values: VectorMapValues
|
||||
min: number | null
|
||||
max: number | null
|
||||
markers: VectorMapMarker[]
|
||||
lines: VectorMapLine[]
|
||||
tooltip: VectorMapTooltip
|
||||
zoom: boolean
|
||||
zoomMax: number
|
||||
zoomOnScroll: boolean
|
||||
zoomInLabel: string
|
||||
zoomOutLabel: string
|
||||
label: string | null
|
||||
}
|
||||
|
||||
type ComponentConfigInput = Partial<ComponentConfig>
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'vector-map'
|
||||
const DATA_KEY = `bs.${NAME}`
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
|
||||
const EVENT_RENDERED = `rendered${EVENT_KEY}`
|
||||
const EVENT_UPDATED = `updated${EVENT_KEY}`
|
||||
const EVENT_POINTERMOVE = `pointermove${EVENT_KEY}`
|
||||
const EVENT_POINTERLEAVE = `pointerleave${EVENT_KEY}`
|
||||
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
|
||||
const EVENT_POINTERUP = `pointerup${EVENT_KEY}`
|
||||
const EVENT_POINTERCANCEL = `pointercancel${EVENT_KEY}`
|
||||
const EVENT_DBLCLICK = `dblclick${EVENT_KEY}`
|
||||
const EVENT_CLICK = `click${EVENT_KEY}`
|
||||
|
||||
const CLASS_NAME_SVG = 'vector-map-svg'
|
||||
const CLASS_NAME_REGION = 'vector-map-region'
|
||||
const CLASS_NAME_LINE = 'vector-map-line'
|
||||
const CLASS_NAME_MARKER = 'vector-map-marker'
|
||||
const CLASS_NAME_MARKER_HALO = 'vector-map-marker-halo'
|
||||
const CLASS_NAME_MARKER_DOT = 'vector-map-marker-dot'
|
||||
const CLASS_NAME_TOOLTIP = 'vector-map-tooltip'
|
||||
const CLASS_NAME_ZOOMABLE = 'vector-map-zoomable'
|
||||
const CLASS_NAME_DRAGGING = 'vector-map-dragging'
|
||||
const CLASS_NAME_CONTROLS = 'vector-map-controls'
|
||||
const CLASS_NAME_CONTROL_GROUP = 'btn-group-vertical'
|
||||
const CLASS_NAME_CONTROL = 'btn btn-sm btn-icon'
|
||||
|
||||
const ATTRIBUTE_REGION = 'data-region'
|
||||
const ATTRIBUTE_VALUE = 'data-value'
|
||||
const ATTRIBUTE_MARKER = 'data-marker'
|
||||
const ATTRIBUTE_ACTION = 'data-bs-vector-map-action'
|
||||
|
||||
const SELECTOR_TOOLTIP_TARGET = `.${CLASS_NAME_REGION}, .${CLASS_NAME_MARKER}`
|
||||
const SELECTOR_ACTION = `[${ATTRIBUTE_ACTION}]`
|
||||
|
||||
const ACTION_ZOOM_IN = 'zoom-in'
|
||||
const ACTION_ZOOM_OUT = 'zoom-out'
|
||||
|
||||
// One step of a button or a double click
|
||||
const ZOOM_STEP = 2
|
||||
const ZOOM_DURATION = 200
|
||||
// How much one unit of wheel movement magnifies. A pinch on a trackpad comes
|
||||
// as a wheel event with `ctrlKey` and much smaller numbers.
|
||||
const WHEEL_SPEED = 0.002
|
||||
const WHEEL_SPEED_PINCH = 0.01
|
||||
|
||||
const ICON_ZOOM_IN = 'M12 5v14M5 12h14'
|
||||
const ICON_ZOOM_OUT = 'M5 12h14'
|
||||
|
||||
// How far a line bends away from the straight one, as a share of its length
|
||||
const LINE_CURVE = 0.2
|
||||
const RADIANS = Math.PI / 180
|
||||
|
||||
const SELECTOR_DATA_TOGGLE = `[data-bs-toggle="${NAME}"], [data-tblr-toggle="${NAME}"]`
|
||||
|
||||
// Declared on `.vector-map-region` in scss/ui/_vector-map.scss, where it gets
|
||||
// the `--tblr-` prefix at build time.
|
||||
const CSS_VAR_VALUE = '--tblr-vector-map-value'
|
||||
const SVG_NS = 'http://www.w3.org/2000/svg'
|
||||
|
||||
const Default: ComponentConfig = {
|
||||
map: 'world',
|
||||
values: {},
|
||||
min: null,
|
||||
max: null,
|
||||
markers: [],
|
||||
lines: [],
|
||||
tooltip: true,
|
||||
zoom: false,
|
||||
zoomMax: 8,
|
||||
zoomOnScroll: true,
|
||||
zoomInLabel: 'Zoom in',
|
||||
zoomOutLabel: 'Zoom out',
|
||||
label: null,
|
||||
}
|
||||
|
||||
const DefaultType: Record<keyof ComponentConfig, string> = {
|
||||
map: '(string|object)',
|
||||
values: 'object',
|
||||
min: '(number|null)',
|
||||
max: '(number|null)',
|
||||
markers: 'array',
|
||||
lines: 'array',
|
||||
tooltip: '(boolean|function)',
|
||||
zoom: 'boolean',
|
||||
zoomMax: 'number',
|
||||
zoomOnScroll: 'boolean',
|
||||
zoomInLabel: 'string',
|
||||
zoomOutLabel: 'string',
|
||||
label: '(string|null)',
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
const svgEl = <K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] => document.createElementNS(SVG_NS, tag)
|
||||
|
||||
// Keeps the entries that are a real number, so a `null` or a typo in the data
|
||||
// leaves that region without a value instead of painting it as zero.
|
||||
const toValues = (input: unknown): VectorMapValues => {
|
||||
const values: VectorMapValues = {}
|
||||
if (typeof input !== 'object' || input === null) {
|
||||
return values
|
||||
}
|
||||
|
||||
for (const [code, value] of Object.entries(input)) {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
values[code] = value
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
const isRecord = (input: unknown): input is Record<string, unknown> => typeof input === 'object' && input !== null
|
||||
|
||||
// Keeps the markers with real coordinates; one with a typo is left out.
|
||||
const toMarkers = (input: unknown): VectorMapMarker[] => {
|
||||
const markers: VectorMapMarker[] = []
|
||||
for (const item of Array.isArray(input) ? (input as unknown[]) : []) {
|
||||
if (isRecord(item) && typeof item.lat === 'number' && typeof item.lng === 'number' && Number.isFinite(item.lat) && Number.isFinite(item.lng)) {
|
||||
markers.push({ lat: item.lat, lng: item.lng, ...(typeof item.name === 'string' ? { name: item.name } : {}) })
|
||||
}
|
||||
}
|
||||
|
||||
return markers
|
||||
}
|
||||
|
||||
const toLines = (input: unknown): VectorMapLine[] => {
|
||||
const lines: VectorMapLine[] = []
|
||||
for (const item of Array.isArray(input) ? (input as unknown[]) : []) {
|
||||
if (isRecord(item) && typeof item.from === 'string' && typeof item.to === 'string') {
|
||||
lines.push({ from: item.from, to: item.to })
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// The same projection the map file was generated with (.build/generate-maps.ts)
|
||||
const project = ({ projection }: VectorMapData, lng: number, lat: number): Point => {
|
||||
const { scale, translate } = projection
|
||||
const y = 1.25 * Math.log(Math.tan(Math.PI / 4 + 0.4 * lat * RADIANS))
|
||||
return [lng * RADIANS * scale + translate[0], translate[1] - y * scale]
|
||||
}
|
||||
|
||||
const round = (value: number): number => Math.round(value * 10) / 10
|
||||
|
||||
const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max)
|
||||
|
||||
const reducedMotion = (): boolean => window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*
|
||||
* A map drawn as inline SVG from a map file, with regions shaded by value,
|
||||
* markers placed by their coordinates and lines between them:
|
||||
*
|
||||
* <div class="vector-map" data-bs-toggle="vector-map" data-bs-map="world" data-bs-values='{"PL": 80, "DE": 45}'></div>
|
||||
*
|
||||
* The shapes come projected from the map file (`dist/js/maps/world.js`), so
|
||||
* only the markers are projected here. Colours and sizes come from the
|
||||
* `--tblr-vector-map-*` custom properties.
|
||||
*
|
||||
* With `data-bs-zoom="true"` the map can be magnified with its buttons, the
|
||||
* wheel, a double click or a pinch, and dragged around.
|
||||
*/
|
||||
|
||||
class VectorMap extends BaseComponent {
|
||||
declare _element: HTMLElement
|
||||
declare _config: ComponentConfig
|
||||
_userConfig: ComponentConfigInput = {}
|
||||
_map: VectorMapData | null = null
|
||||
_tooltip: HTMLElement | null = null
|
||||
_view: View = { x: 0, y: 0, scale: 1 }
|
||||
_frame = 0
|
||||
// Pointers that are down on the map, with where each was last seen
|
||||
_pointers = new Map<number, Point>()
|
||||
|
||||
/** Maps added with `addMap()`, by name */
|
||||
static maps: Record<string, VectorMapData> = {}
|
||||
|
||||
constructor(element: ElementSelector, config?: ComponentConfigInput) {
|
||||
super(element, config)
|
||||
|
||||
if (!this._element) {
|
||||
return
|
||||
}
|
||||
|
||||
// Kept so that render() can re-read the data attributes on top of it.
|
||||
this._userConfig = { ...config }
|
||||
this.render()
|
||||
|
||||
EventHandler.on(this._element, EVENT_POINTERMOVE, (event: PointerEvent) => {
|
||||
this._onPointerMove(event)
|
||||
})
|
||||
EventHandler.on(this._element, EVENT_POINTERLEAVE, () => {
|
||||
this._hideTooltip()
|
||||
})
|
||||
EventHandler.on(this._element, EVENT_POINTERDOWN, (event: PointerEvent) => {
|
||||
this._onPointerDown(event)
|
||||
})
|
||||
for (const eventName of [EVENT_POINTERUP, EVENT_POINTERCANCEL]) {
|
||||
EventHandler.on(this._element, eventName, (event: PointerEvent) => {
|
||||
this._onPointerUp(event)
|
||||
})
|
||||
}
|
||||
|
||||
// `wheel` is not among the native events EventHandler knows, and the
|
||||
// listener must not be passive to keep the page from scrolling.
|
||||
this._element.addEventListener('wheel', this._onWheel, { passive: false })
|
||||
EventHandler.on(this._element, EVENT_DBLCLICK, (event: MouseEvent) => {
|
||||
if (this._config.zoom && this._isOnMap(event)) {
|
||||
this._zoomBy(ZOOM_STEP, this._toMapPoint(event.clientX, event.clientY), true)
|
||||
}
|
||||
})
|
||||
EventHandler.on(this._element, EVENT_CLICK, SELECTOR_ACTION, (event: Event) => {
|
||||
const button = (event.target as Element).closest(SELECTOR_ACTION)
|
||||
if (button?.getAttribute(ATTRIBUTE_ACTION) === ACTION_ZOOM_IN) {
|
||||
this.zoomIn()
|
||||
} else {
|
||||
this.zoomOut()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default(): ComponentConfig {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType(): Record<keyof ComponentConfig, string> {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME(): string {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Static
|
||||
static addMap(name: string, data: VectorMapData): void {
|
||||
VectorMap.maps[name] = data
|
||||
}
|
||||
|
||||
// Public
|
||||
render(): void {
|
||||
this._config = this._getConfig(this._userConfig) as ComponentConfig
|
||||
const map = (this._map = this._getMap())
|
||||
const { label } = this._config
|
||||
|
||||
const svg = svgEl('svg')
|
||||
svg.setAttribute('class', CLASS_NAME_SVG)
|
||||
svg.setAttribute('viewBox', `0 0 ${map.width} ${map.height}`)
|
||||
|
||||
// Without a label the map says nothing to a screen reader, so it is hidden
|
||||
// rather than announced as an unnamed image.
|
||||
if (label) {
|
||||
svg.setAttribute('role', 'img')
|
||||
svg.setAttribute('aria-label', label)
|
||||
} else {
|
||||
svg.setAttribute('aria-hidden', 'true')
|
||||
}
|
||||
|
||||
for (const [code, region] of Object.entries(map.regions)) {
|
||||
const path = svgEl('path')
|
||||
path.setAttribute('class', CLASS_NAME_REGION)
|
||||
path.setAttribute(ATTRIBUTE_REGION, code)
|
||||
path.setAttribute('d', region.path)
|
||||
svg.append(path)
|
||||
}
|
||||
|
||||
this._renderMarkers(svg, map)
|
||||
|
||||
cancelAnimationFrame(this._frame)
|
||||
this._pointers.clear()
|
||||
this._hideTooltip()
|
||||
this._getSvg()?.remove()
|
||||
this._element.append(svg)
|
||||
this._renderControls()
|
||||
this._setView({ x: 0, y: 0, scale: 1 })
|
||||
this._applyValues()
|
||||
|
||||
EventHandler.trigger(this._element, EVENT_RENDERED)
|
||||
}
|
||||
|
||||
update(values: VectorMapValues): void {
|
||||
this._userConfig.values = values
|
||||
this._config = this._getConfig(this._userConfig) as ComponentConfig
|
||||
this._applyValues()
|
||||
|
||||
EventHandler.trigger(this._element, EVENT_UPDATED)
|
||||
}
|
||||
|
||||
zoomIn(): void {
|
||||
this._zoomBy(ZOOM_STEP, null, true)
|
||||
}
|
||||
|
||||
zoomOut(): void {
|
||||
this._zoomBy(1 / ZOOM_STEP, null, true)
|
||||
}
|
||||
|
||||
// Back to the whole map
|
||||
reset(): void {
|
||||
this._animateTo({ x: 0, y: 0, scale: 1 })
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
cancelAnimationFrame(this._frame)
|
||||
this._getSvg()?.remove()
|
||||
this._getControls()?.remove()
|
||||
this._tooltip?.remove()
|
||||
this._element.classList.remove(CLASS_NAME_ZOOMABLE, CLASS_NAME_DRAGGING)
|
||||
this._element.removeEventListener('wheel', this._onWheel)
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
// Private
|
||||
_configAfterMerge(config: BaseConfig): BaseConfig {
|
||||
config.values = toValues(config.values)
|
||||
config.markers = toMarkers(config.markers)
|
||||
config.lines = toLines(config.lines)
|
||||
return config
|
||||
}
|
||||
|
||||
_getSvg(): SVGSVGElement | null {
|
||||
return this._element.querySelector<SVGSVGElement>(`.${CLASS_NAME_SVG}`)
|
||||
}
|
||||
|
||||
_getMap(): VectorMapData {
|
||||
const { map } = this._config
|
||||
if (typeof map !== 'string') {
|
||||
return map
|
||||
}
|
||||
|
||||
// `tablerVectorMaps` is what a map file loaded with a `<script>` tag fills.
|
||||
const data = VectorMap.maps[map] ?? window.tablerVectorMaps?.[map]
|
||||
if (!data) {
|
||||
throw new TypeError(`${NAME.toUpperCase()}: Map "${map}" is not loaded. Add its file from "dist/js/maps/" or call VectorMap.addMap().`)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Lines go in first, so the markers they join sit on top of their ends.
|
||||
_renderMarkers(svg: SVGSVGElement, map: VectorMapData): void {
|
||||
const { markers, lines } = this._config
|
||||
const points = markers.map((marker) => project(map, marker.lng, marker.lat))
|
||||
const pointOf = (name: string): Point | undefined => points[markers.findIndex((marker) => marker.name === name)]
|
||||
|
||||
for (const line of lines) {
|
||||
const from = pointOf(line.from)
|
||||
const to = pointOf(line.to)
|
||||
if (!from || !to) {
|
||||
continue
|
||||
}
|
||||
|
||||
// A curve through a point pushed sideways from the middle of the line
|
||||
const controlX = (from[0] + to[0]) / 2 + (to[1] - from[1]) * LINE_CURVE
|
||||
const controlY = (from[1] + to[1]) / 2 - (to[0] - from[0]) * LINE_CURVE
|
||||
const path = svgEl('path')
|
||||
path.setAttribute('class', CLASS_NAME_LINE)
|
||||
path.setAttribute('d', `M${round(from[0])} ${round(from[1])}Q${round(controlX)} ${round(controlY)} ${round(to[0])} ${round(to[1])}`)
|
||||
svg.append(path)
|
||||
}
|
||||
|
||||
for (const [index, marker] of markers.entries()) {
|
||||
// A line of no length with round caps is drawn as a dot, and unlike a
|
||||
// circle it keeps its size in pixels however large the map is.
|
||||
const d = `M${round(points[index][0])} ${round(points[index][1])}h0`
|
||||
const group = svgEl('g')
|
||||
group.setAttribute('class', CLASS_NAME_MARKER)
|
||||
group.setAttribute(ATTRIBUTE_MARKER, marker.name ?? '')
|
||||
|
||||
for (const className of [CLASS_NAME_MARKER_HALO, CLASS_NAME_MARKER_DOT]) {
|
||||
const path = svgEl('path')
|
||||
path.setAttribute('class', className)
|
||||
path.setAttribute('d', d)
|
||||
group.append(path)
|
||||
}
|
||||
|
||||
svg.append(group)
|
||||
}
|
||||
}
|
||||
|
||||
_tooltipText(target: Element): string {
|
||||
const { tooltip, values } = this._config
|
||||
if (!tooltip) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let item: VectorMapTooltipItem
|
||||
const code = target.getAttribute(ATTRIBUTE_REGION)
|
||||
if (code === null) {
|
||||
item = { type: 'marker', name: target.getAttribute(ATTRIBUTE_MARKER) ?? '' }
|
||||
} else {
|
||||
const value = values[code]
|
||||
item = { type: 'region', code, name: this._map?.regions[code]?.name ?? code, ...(value === undefined ? {} : { value }) }
|
||||
}
|
||||
|
||||
if (typeof tooltip === 'function') {
|
||||
return tooltip(item)
|
||||
}
|
||||
|
||||
return item.type === 'region' && item.value !== undefined ? `${item.name}: ${item.value}` : item.name
|
||||
}
|
||||
|
||||
_getControls(): HTMLElement | null {
|
||||
return this._element.querySelector<HTMLElement>(`.${CLASS_NAME_CONTROLS}`)
|
||||
}
|
||||
|
||||
// The zoom buttons, laid over a corner of the map
|
||||
_renderControls(): void {
|
||||
const { zoom, zoomInLabel, zoomOutLabel } = this._config
|
||||
this._getControls()?.remove()
|
||||
this._element.classList.toggle(CLASS_NAME_ZOOMABLE, zoom)
|
||||
|
||||
if (!zoom) {
|
||||
return
|
||||
}
|
||||
|
||||
const controls = document.createElement('div')
|
||||
controls.className = `${CLASS_NAME_CONTROLS} ${CLASS_NAME_CONTROL_GROUP}`
|
||||
|
||||
for (const [action, label, icon] of [
|
||||
[ACTION_ZOOM_IN, zoomInLabel, ICON_ZOOM_IN],
|
||||
[ACTION_ZOOM_OUT, zoomOutLabel, ICON_ZOOM_OUT],
|
||||
]) {
|
||||
const button = document.createElement('button')
|
||||
button.type = 'button'
|
||||
button.className = CLASS_NAME_CONTROL
|
||||
button.setAttribute(ATTRIBUTE_ACTION, action)
|
||||
button.setAttribute('aria-label', label)
|
||||
|
||||
const svg = svgEl('svg')
|
||||
svg.setAttribute('class', 'icon')
|
||||
svg.setAttribute('viewBox', '0 0 24 24')
|
||||
svg.setAttribute('aria-hidden', 'true')
|
||||
const path = svgEl('path')
|
||||
path.setAttribute('d', icon)
|
||||
svg.append(path)
|
||||
button.append(svg)
|
||||
controls.append(button)
|
||||
}
|
||||
|
||||
this._element.append(controls)
|
||||
}
|
||||
|
||||
// Shows the given part of the map, kept inside the zoom range and the map
|
||||
// itself, so the map can never be dragged out of sight.
|
||||
_setView({ x, y, scale }: View): void {
|
||||
const svg = this._getSvg()
|
||||
if (!this._map || !svg) {
|
||||
return
|
||||
}
|
||||
|
||||
const { width, height } = this._map
|
||||
const max = Math.max(this._config.zoomMax, 1)
|
||||
scale = clamp(scale, 1, max)
|
||||
const viewWidth = width / scale
|
||||
const viewHeight = height / scale
|
||||
x = clamp(x, 0, width - viewWidth)
|
||||
y = clamp(y, 0, height - viewHeight)
|
||||
|
||||
this._view = { x, y, scale }
|
||||
svg.setAttribute('viewBox', `${round(x)} ${round(y)} ${round(viewWidth)} ${round(viewHeight)}`)
|
||||
|
||||
for (const button of this._element.querySelectorAll<HTMLButtonElement>(SELECTOR_ACTION)) {
|
||||
button.disabled = button.getAttribute(ATTRIBUTE_ACTION) === ACTION_ZOOM_IN ? scale >= max : scale <= 1
|
||||
}
|
||||
}
|
||||
|
||||
// Moves to a view over a few frames. The corner and the size of the view
|
||||
// change at the same pace, which keeps the point being zoomed at in place.
|
||||
_animateTo(target: View): void {
|
||||
cancelAnimationFrame(this._frame)
|
||||
|
||||
if (!this._map || reducedMotion()) {
|
||||
this._setView(target)
|
||||
return
|
||||
}
|
||||
|
||||
const { width } = this._map
|
||||
const from = this._view
|
||||
const start = performance.now()
|
||||
|
||||
const step = (now: number): void => {
|
||||
const t = Math.min((now - start) / ZOOM_DURATION, 1)
|
||||
const eased = 1 - (1 - t) ** 3
|
||||
const viewWidth = width / from.scale + (width / target.scale - width / from.scale) * eased
|
||||
this._setView({ x: from.x + (target.x - from.x) * eased, y: from.y + (target.y - from.y) * eased, scale: width / viewWidth })
|
||||
|
||||
if (t < 1) {
|
||||
this._frame = requestAnimationFrame(step)
|
||||
}
|
||||
}
|
||||
|
||||
this._frame = requestAnimationFrame(step)
|
||||
}
|
||||
|
||||
// Magnifies by a factor around a point of the map, which stays where it is
|
||||
// on the screen. Without a point, around the middle of the view.
|
||||
_zoomBy(factor: number, anchor: Point | null = null, animate = false): void {
|
||||
if (!this._map) {
|
||||
return
|
||||
}
|
||||
|
||||
const { width, height } = this._map
|
||||
const { x, y, scale } = this._view
|
||||
const next = clamp(scale * factor, 1, Math.max(this._config.zoomMax, 1))
|
||||
const [anchorX, anchorY] = anchor ?? [x + width / scale / 2, y + height / scale / 2]
|
||||
const target = { x: anchorX - ((anchorX - x) * scale) / next, y: anchorY - ((anchorY - y) * scale) / next, scale: next }
|
||||
|
||||
if (animate) {
|
||||
this._animateTo(target)
|
||||
} else {
|
||||
cancelAnimationFrame(this._frame)
|
||||
this._setView(target)
|
||||
}
|
||||
}
|
||||
|
||||
// A point of the screen in the coordinates of the map
|
||||
_toMapPoint(clientX: number, clientY: number): Point {
|
||||
const matrix = this._getSvg()?.getScreenCTM()
|
||||
if (!matrix) {
|
||||
return [0, 0]
|
||||
}
|
||||
|
||||
const point = new DOMPoint(clientX, clientY).matrixTransform(matrix.inverse())
|
||||
return [point.x, point.y]
|
||||
}
|
||||
|
||||
// Whether an event happened on the drawing, not on a button over it
|
||||
_isOnMap(event: Event): boolean {
|
||||
return event.target instanceof Element && event.target.closest(`.${CLASS_NAME_SVG}`) !== null
|
||||
}
|
||||
|
||||
_onPointerDown(event: PointerEvent): void {
|
||||
if (!this._config.zoom || !this._isOnMap(event) || (event.pointerType === 'mouse' && event.button !== 0)) {
|
||||
return
|
||||
}
|
||||
|
||||
this._pointers.set(event.pointerId, [event.clientX, event.clientY])
|
||||
cancelAnimationFrame(this._frame)
|
||||
|
||||
// The map keeps getting the moves of this pointer when it leaves the map.
|
||||
// Capturing throws for a pointer the browser does not know, which is what
|
||||
// an event made in a script has.
|
||||
try {
|
||||
this._getSvg()?.setPointerCapture(event.pointerId)
|
||||
} catch {
|
||||
// The drag still works while the pointer stays over the map
|
||||
}
|
||||
}
|
||||
|
||||
_onPointerUp(event: PointerEvent): void {
|
||||
this._pointers.delete(event.pointerId)
|
||||
if (this._pointers.size === 0) {
|
||||
this._element.classList.remove(CLASS_NAME_DRAGGING)
|
||||
}
|
||||
}
|
||||
|
||||
// One pointer drags the map, two pinch it.
|
||||
_drag(event: PointerEvent): void {
|
||||
const previous = this._pointers.get(event.pointerId)
|
||||
const matrix = this._getSvg()?.getScreenCTM()
|
||||
if (!previous || !matrix) {
|
||||
return
|
||||
}
|
||||
|
||||
const current: Point = [event.clientX, event.clientY]
|
||||
this._pointers.set(event.pointerId, current)
|
||||
this._element.classList.add(CLASS_NAME_DRAGGING)
|
||||
this._hideTooltip()
|
||||
|
||||
if (this._pointers.size === 1) {
|
||||
// `matrix.a` is how many pixels one unit of the map takes
|
||||
const { x, y, scale } = this._view
|
||||
this._setView({ x: x - (current[0] - previous[0]) / matrix.a, y: y - (current[1] - previous[1]) / matrix.d, scale })
|
||||
return
|
||||
}
|
||||
|
||||
const other = [...this._pointers].find(([id]) => id !== event.pointerId)?.[1]
|
||||
if (!other) {
|
||||
return
|
||||
}
|
||||
|
||||
const before = Math.hypot(previous[0] - other[0], previous[1] - other[1])
|
||||
const after = Math.hypot(current[0] - other[0], current[1] - other[1])
|
||||
if (before > 0 && after > 0) {
|
||||
this._zoomBy(after / before, this._toMapPoint((current[0] + other[0]) / 2, (current[1] + other[1]) / 2))
|
||||
}
|
||||
}
|
||||
|
||||
_onWheel = (event: WheelEvent): void => {
|
||||
const { zoom, zoomOnScroll } = this._config
|
||||
if (!zoom || !zoomOnScroll || !this._isOnMap(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
// The page must not scroll while the wheel zooms the map
|
||||
event.preventDefault()
|
||||
this._hideTooltip()
|
||||
const speed = event.ctrlKey ? WHEEL_SPEED_PINCH : WHEEL_SPEED
|
||||
this._zoomBy(Math.exp(-event.deltaY * speed), this._toMapPoint(event.clientX, event.clientY))
|
||||
}
|
||||
|
||||
_onPointerMove(event: PointerEvent): void {
|
||||
if (this._pointers.has(event.pointerId)) {
|
||||
this._drag(event)
|
||||
return
|
||||
}
|
||||
|
||||
const target = event.target instanceof Element ? event.target.closest(SELECTOR_TOOLTIP_TARGET) : null
|
||||
const text = target ? this._tooltipText(target) : ''
|
||||
if (!text) {
|
||||
this._hideTooltip()
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._tooltip) {
|
||||
// For the eye only: the text repeats what the pointer is over
|
||||
this._tooltip = document.createElement('div')
|
||||
this._tooltip.className = CLASS_NAME_TOOLTIP
|
||||
this._tooltip.setAttribute('aria-hidden', 'true')
|
||||
this._element.append(this._tooltip)
|
||||
}
|
||||
|
||||
const rect = this._element.getBoundingClientRect()
|
||||
this._tooltip.textContent = text
|
||||
this._tooltip.hidden = false
|
||||
this._tooltip.style.left = `${event.clientX - rect.left}px`
|
||||
this._tooltip.style.top = `${event.clientY - rect.top}px`
|
||||
}
|
||||
|
||||
_hideTooltip(): void {
|
||||
if (this._tooltip) {
|
||||
this._tooltip.hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
// Shades every region by where its value sits between `min` and `max`. Only
|
||||
// that 0 to 1 ratio is written here; the colour is mixed in CSS.
|
||||
_applyValues(): void {
|
||||
const { values, min: minForced, max: maxForced } = this._config
|
||||
const numbers = Object.values(values)
|
||||
const min = minForced ?? Math.min(...numbers)
|
||||
const max = maxForced ?? Math.max(...numbers)
|
||||
const span = max - min
|
||||
|
||||
for (const path of this._element.querySelectorAll<SVGPathElement>(`.${CLASS_NAME_REGION}`)) {
|
||||
const value = values[path.getAttribute(ATTRIBUTE_REGION) ?? '']
|
||||
|
||||
if (value === undefined) {
|
||||
path.removeAttribute(ATTRIBUTE_VALUE)
|
||||
path.style.removeProperty(CSS_VAR_VALUE)
|
||||
continue
|
||||
}
|
||||
|
||||
// With a single value, or all of them equal, everything gets the full colour.
|
||||
const ratio = span === 0 ? 1 : Math.min(Math.max((value - min) / span, 0), 1)
|
||||
path.setAttribute(ATTRIBUTE_VALUE, String(value))
|
||||
path.style.setProperty(CSS_VAR_VALUE, String(Math.round(ratio * 1000) / 1000))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
// A map file may come later in the page than this script, also when both are
|
||||
// deferred, so the maps are drawn once the document is parsed and not before.
|
||||
// The `load` listener covers a script added after that point; drawing twice is
|
||||
// harmless, an element keeps its one instance.
|
||||
// js-docs-start vector-map-init
|
||||
const init = (): void => {
|
||||
initAll(SELECTOR_DATA_TOGGLE, VectorMap)
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
init()
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', init, { once: true })
|
||||
window.addEventListener('load', init, { once: true })
|
||||
}
|
||||
// js-docs-end vector-map-init
|
||||
|
||||
export default VectorMap
|
||||
@@ -0,0 +1,7 @@
|
||||
// The vector map ships as its own bundle, so pages without a map do not carry
|
||||
// it. Load it after `tabler.js`: the UMD build adds `VectorMap` to the `tabler`
|
||||
// global. The maps themselves are separate files in `dist/js/maps/`.
|
||||
import './src/vector-map'
|
||||
|
||||
export { default as VectorMap } from './src/vector-map'
|
||||
export type { VectorMapData, VectorMapLine, VectorMapMarker, VectorMapRegion, VectorMapTooltipItem } from './src/vector-map'
|
||||
@@ -0,0 +1,519 @@
|
||||
import { describe, it, expect, beforeAll, afterEach, vi } from 'vitest'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture'
|
||||
import VectorMap from '../../src/vector-map'
|
||||
import type { VectorMapData } from '../../src/vector-map'
|
||||
import { world } from '../../maps/world'
|
||||
|
||||
const squares: VectorMapData = {
|
||||
width: 30,
|
||||
height: 10,
|
||||
projection: { type: 'miller', scale: 1, translate: [0, 0] },
|
||||
regions: {
|
||||
AA: { name: 'First', path: 'M0 0l10 0 0 10-10 0z' },
|
||||
BB: { name: 'Second', path: 'M10 0l10 0 0 10-10 0z' },
|
||||
CC: { name: 'Third', path: 'M20 0l10 0 0 10-10 0z' },
|
||||
},
|
||||
}
|
||||
|
||||
describe('VectorMap', () => {
|
||||
let fixtureEl: HTMLElement
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
VectorMap.maps = {}
|
||||
delete window.tablerVectorMaps
|
||||
})
|
||||
|
||||
const el = (): HTMLElement => fixtureEl.querySelector('.vector-map')!
|
||||
const region = (code: string): SVGPathElement => el().querySelector<SVGPathElement>(`[data-region="${code}"]`)!
|
||||
const ratio = (code: string): string => region(code).style.getPropertyValue('--tblr-vector-map-value')
|
||||
|
||||
describe('NAME', () => {
|
||||
it('should return plugin name', () => {
|
||||
expect(VectorMap.NAME).toBe('vector-map')
|
||||
})
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should render one path per region of the map', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" data-bs-map="squares"></div>'
|
||||
VectorMap.addMap('squares', squares)
|
||||
|
||||
const spy = vi.fn()
|
||||
el().addEventListener('rendered.bs.vector-map', spy)
|
||||
new VectorMap(el())
|
||||
|
||||
const svg = el().querySelector('svg')!
|
||||
expect(svg.getAttribute('viewBox')).toBe('0 0 30 10')
|
||||
expect(svg.querySelectorAll('path.vector-map-region').length).toBe(3)
|
||||
expect(region('BB').getAttribute('d')).toBe(squares.regions.BB.path)
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should take the map as an object', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares })
|
||||
|
||||
expect(el().querySelectorAll('path').length).toBe(3)
|
||||
})
|
||||
|
||||
it('should find a map loaded with a script tag', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" data-bs-map="squares"></div>'
|
||||
window.tablerVectorMaps = { squares }
|
||||
|
||||
new VectorMap(el())
|
||||
|
||||
expect(el().querySelectorAll('path').length).toBe(3)
|
||||
})
|
||||
|
||||
it('should throw when the map is not loaded', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" data-bs-map="nowhere"></div>'
|
||||
|
||||
expect(() => new VectorMap(el())).toThrowError('VECTOR-MAP: Map "nowhere" is not loaded.')
|
||||
})
|
||||
|
||||
it('should name the map for assistive technology when it has a label', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" data-bs-label="Sales by country"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares })
|
||||
|
||||
const svg = el().querySelector('svg')!
|
||||
expect(svg.getAttribute('role')).toBe('img')
|
||||
expect(svg.getAttribute('aria-label')).toBe('Sales by country')
|
||||
expect(svg.hasAttribute('aria-hidden')).toBe(false)
|
||||
})
|
||||
|
||||
it('should hide the map from assistive technology without a label', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares })
|
||||
|
||||
expect(el().querySelector('svg')!.getAttribute('aria-hidden')).toBe('true')
|
||||
})
|
||||
})
|
||||
|
||||
describe('values', () => {
|
||||
it('should shade regions by their place between the lowest and highest value', () => {
|
||||
fixtureEl.innerHTML = `<div class="vector-map" data-bs-values='{"AA": 10, "BB": 20, "CC": 50}'></div>`
|
||||
|
||||
new VectorMap(el(), { map: squares })
|
||||
|
||||
expect(region('AA').getAttribute('data-value')).toBe('10')
|
||||
expect(ratio('AA')).toBe('0')
|
||||
expect(ratio('BB')).toBe('0.25')
|
||||
expect(ratio('CC')).toBe('1')
|
||||
})
|
||||
|
||||
it('should leave a region without a value untouched', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares, values: { AA: 1, ZZ: 5 } })
|
||||
|
||||
expect(region('BB').hasAttribute('data-value')).toBe(false)
|
||||
expect(ratio('BB')).toBe('')
|
||||
})
|
||||
|
||||
it('should use the forced range and clamp values outside of it', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" data-bs-min="0" data-bs-max="100"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares, values: { AA: 25, BB: 150, CC: -10 } })
|
||||
|
||||
expect(ratio('AA')).toBe('0.25')
|
||||
expect(ratio('BB')).toBe('1')
|
||||
expect(ratio('CC')).toBe('0')
|
||||
})
|
||||
|
||||
it('should give the full colour when all values are equal', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares, values: { AA: 7, BB: 7 } })
|
||||
|
||||
expect(ratio('AA')).toBe('1')
|
||||
expect(ratio('BB')).toBe('1')
|
||||
})
|
||||
|
||||
it('should ignore values that are not a number', () => {
|
||||
fixtureEl.innerHTML = `<div class="vector-map" data-bs-values='{"AA": 10, "BB": null, "CC": "many"}'></div>`
|
||||
|
||||
new VectorMap(el(), { map: squares })
|
||||
|
||||
expect(region('AA').hasAttribute('data-value')).toBe(true)
|
||||
expect(region('BB').hasAttribute('data-value')).toBe(false)
|
||||
expect(region('CC').hasAttribute('data-value')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('update', () => {
|
||||
it('should replace the values without drawing the map again', () => {
|
||||
fixtureEl.innerHTML = `<div class="vector-map" data-bs-values='{"AA": 10, "BB": 20}'></div>`
|
||||
|
||||
const map = new VectorMap(el(), { map: squares })
|
||||
const svg = el().querySelector('svg')
|
||||
const spy = vi.fn()
|
||||
el().addEventListener('updated.bs.vector-map', spy)
|
||||
|
||||
map.update({ BB: 1, CC: 3 })
|
||||
|
||||
expect(el().querySelector('svg')).toBe(svg)
|
||||
expect(region('AA').hasAttribute('data-value')).toBe(false)
|
||||
expect(ratio('AA')).toBe('')
|
||||
expect(ratio('BB')).toBe('0')
|
||||
expect(ratio('CC')).toBe('1')
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('markers', () => {
|
||||
it('should place a marker by its coordinates', () => {
|
||||
fixtureEl.innerHTML = `<div class="vector-map" data-bs-markers='[{"name": "Null Island", "lat": 0, "lng": 0}]'></div>`
|
||||
|
||||
new VectorMap(el(), { map: world })
|
||||
|
||||
const marker = el().querySelector('.vector-map-marker')!
|
||||
expect(marker.getAttribute('data-marker')).toBe('Null Island')
|
||||
expect(marker.querySelector('.vector-map-marker-halo')!.getAttribute('d')).toBe('M500 316h0')
|
||||
expect(marker.querySelector('.vector-map-marker-dot')!.getAttribute('d')).toBe('M500 316h0')
|
||||
})
|
||||
|
||||
it('should put a marker inside the country it belongs to', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" style="width: 500px"></div>'
|
||||
|
||||
new VectorMap(el(), { map: world, markers: [{ name: 'Warsaw', lat: 52.23, lng: 21.01 }] })
|
||||
|
||||
const [x, y] = el().querySelector('.vector-map-marker-dot')!.getAttribute('d')!.slice(1, -2).split(' ').map(Number)
|
||||
const svg = el().querySelector('svg')!
|
||||
const point = svg.createSVGPoint()
|
||||
point.x = x
|
||||
point.y = y
|
||||
expect(region('PL').isPointInFill(point)).toBe(true)
|
||||
expect(region('DE').isPointInFill(point)).toBe(false)
|
||||
})
|
||||
|
||||
it('should leave out a marker without real coordinates', () => {
|
||||
fixtureEl.innerHTML = `<div class="vector-map" data-bs-markers='[{"name": "A", "lat": 1, "lng": 2}, {"name": "B", "lat": "north"}, null]'></div>`
|
||||
|
||||
new VectorMap(el(), { map: squares })
|
||||
|
||||
expect(el().querySelectorAll('.vector-map-marker').length).toBe(1)
|
||||
})
|
||||
|
||||
it('should draw a curve between two markers, under the markers', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), {
|
||||
map: world,
|
||||
markers: [
|
||||
{ name: 'A', lat: 0, lng: 0 },
|
||||
{ name: 'B', lat: 0, lng: 90 },
|
||||
],
|
||||
lines: [{ from: 'A', to: 'B' }],
|
||||
})
|
||||
|
||||
const line = el().querySelector('.vector-map-line')!
|
||||
expect(line.getAttribute('d')).toBe('M500 316Q625 266 750 316')
|
||||
expect(line.compareDocumentPosition(el().querySelector('.vector-map-marker')!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should skip a line to a marker that does not exist', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares, markers: [{ name: 'A', lat: 0, lng: 0 }], lines: [{ from: 'A', to: 'Nowhere' }] })
|
||||
|
||||
expect(el().querySelector('.vector-map-line')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('tooltip', () => {
|
||||
const tooltip = (): HTMLElement | null => el().querySelector<HTMLElement>('.vector-map-tooltip')
|
||||
const moveOver = (target: Element): void => {
|
||||
target.dispatchEvent(new PointerEvent('pointermove', { bubbles: true, clientX: 10, clientY: 10 }))
|
||||
}
|
||||
|
||||
it('should show the name of the region under the pointer, with its value', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares, values: { AA: 5 } })
|
||||
expect(tooltip()).toBeNull()
|
||||
|
||||
moveOver(region('AA'))
|
||||
expect(tooltip()!.textContent).toBe('First: 5')
|
||||
expect(tooltip()!.hidden).toBe(false)
|
||||
|
||||
moveOver(region('BB'))
|
||||
expect(tooltip()!.textContent).toBe('Second')
|
||||
expect(el().querySelectorAll('.vector-map-tooltip').length).toBe(1)
|
||||
})
|
||||
|
||||
it('should show the name of a marker', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares, markers: [{ name: 'Capital', lat: 0, lng: 0 }] })
|
||||
moveOver(el().querySelector('.vector-map-marker-dot')!)
|
||||
|
||||
expect(tooltip()!.textContent).toBe('Capital')
|
||||
})
|
||||
|
||||
it('should hide when the pointer leaves the map', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares })
|
||||
moveOver(region('AA'))
|
||||
el().dispatchEvent(new PointerEvent('pointerleave'))
|
||||
|
||||
expect(tooltip()!.hidden).toBe(true)
|
||||
})
|
||||
|
||||
it('should not show when turned off', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" data-bs-tooltip="false"></div>'
|
||||
|
||||
new VectorMap(el(), { map: squares })
|
||||
moveOver(region('AA'))
|
||||
|
||||
expect(tooltip()).toBeNull()
|
||||
})
|
||||
|
||||
it('should take the text from a function, as plain text', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
new VectorMap(el(), {
|
||||
map: squares,
|
||||
values: { AA: 5 },
|
||||
tooltip: (item) => (item.type === 'region' && item.value !== undefined ? `<b>${item.code}</b> ${item.value} %` : ''),
|
||||
})
|
||||
|
||||
moveOver(region('AA'))
|
||||
expect(tooltip()!.textContent).toBe('<b>AA</b> 5 %')
|
||||
expect(tooltip()!.querySelector('b')).toBeNull()
|
||||
|
||||
moveOver(region('BB'))
|
||||
expect(tooltip()!.hidden).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('zoom', () => {
|
||||
const svg = (): SVGSVGElement => el().querySelector<SVGSVGElement>('.vector-map-svg')!
|
||||
const viewBox = (): number[] => svg().getAttribute('viewBox')!.split(' ').map(Number)
|
||||
const button = (action: string): HTMLButtonElement => el().querySelector<HTMLButtonElement>(`[data-bs-vector-map-action="${action}"]`)!
|
||||
const pointer = (type: string, x: number, y: number, pointerId = 1): void => {
|
||||
region('BB').dispatchEvent(new PointerEvent(type, { bubbles: true, pointerId, pointerType: 'mouse', button: 0, clientX: x, clientY: y }))
|
||||
}
|
||||
|
||||
const wheel = (deltaY: number, target: Element = region('BB')): WheelEvent => {
|
||||
const rect = target.getBoundingClientRect()
|
||||
const event = new WheelEvent('wheel', { bubbles: true, cancelable: true, deltaY, clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2 })
|
||||
target.dispatchEvent(event)
|
||||
return event
|
||||
}
|
||||
|
||||
const zoomable = (config = {}): VectorMap => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" style="width: 300px" data-bs-zoom="true"></div>'
|
||||
return new VectorMap(el(), { map: squares, ...config })
|
||||
}
|
||||
|
||||
it('should not react to the wheel or add buttons by default', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" style="width: 300px"></div>'
|
||||
new VectorMap(el(), { map: squares })
|
||||
|
||||
const event = wheel(-500)
|
||||
|
||||
expect(event.defaultPrevented).toBe(false)
|
||||
expect(viewBox()).toEqual([0, 0, 30, 10])
|
||||
expect(el().querySelector('.vector-map-controls')).toBeNull()
|
||||
expect(el().classList.contains('vector-map-zoomable')).toBe(false)
|
||||
})
|
||||
|
||||
it('should add labelled buttons, with zoom out disabled on the whole map', () => {
|
||||
zoomable({ zoomInLabel: 'Closer' })
|
||||
|
||||
expect(el().classList.contains('vector-map-zoomable')).toBe(true)
|
||||
expect(button('zoom-in').getAttribute('aria-label')).toBe('Closer')
|
||||
expect(button('zoom-out').getAttribute('aria-label')).toBe('Zoom out')
|
||||
expect(button('zoom-in').disabled).toBe(false)
|
||||
expect(button('zoom-out').disabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should zoom with the wheel around the pointer and keep the page from scrolling', () => {
|
||||
zoomable()
|
||||
|
||||
// exp(500 * 0.002) is a little under 3 times
|
||||
const event = wheel(-500)
|
||||
|
||||
expect(event.defaultPrevented).toBe(true)
|
||||
const [x, y, width, height] = viewBox()
|
||||
expect(width).toBeCloseTo(30 / Math.E, 1)
|
||||
expect(height).toBeCloseTo(10 / Math.E, 1)
|
||||
// The middle of the map was under the pointer, so it is still in the middle
|
||||
expect(x + width / 2).toBeCloseTo(15, 0)
|
||||
expect(y + height / 2).toBeCloseTo(5, 0)
|
||||
expect(button('zoom-out').disabled).toBe(false)
|
||||
})
|
||||
|
||||
it('should leave the wheel alone with zoomOnScroll off', () => {
|
||||
zoomable({ zoomOnScroll: false })
|
||||
|
||||
expect(wheel(-500).defaultPrevented).toBe(false)
|
||||
expect(viewBox()).toEqual([0, 0, 30, 10])
|
||||
})
|
||||
|
||||
it('should stop at the largest zoom and at the whole map', () => {
|
||||
zoomable({ zoomMax: 2 })
|
||||
|
||||
wheel(-5000)
|
||||
expect(viewBox()[2]).toBe(15)
|
||||
expect(button('zoom-in').disabled).toBe(true)
|
||||
|
||||
wheel(5000)
|
||||
expect(viewBox()).toEqual([0, 0, 30, 10])
|
||||
})
|
||||
|
||||
it('should zoom in steps with the buttons', async () => {
|
||||
zoomable()
|
||||
|
||||
button('zoom-in').click()
|
||||
await vi.waitFor(() => {
|
||||
expect(viewBox()).toEqual([7.5, 2.5, 15, 5])
|
||||
})
|
||||
|
||||
button('zoom-out').click()
|
||||
await vi.waitFor(() => {
|
||||
expect(viewBox()).toEqual([0, 0, 30, 10])
|
||||
})
|
||||
})
|
||||
|
||||
it('should zoom in on a double click', async () => {
|
||||
zoomable()
|
||||
|
||||
region('AA').dispatchEvent(new MouseEvent('dblclick', { bubbles: true, clientX: el().getBoundingClientRect().left, clientY: el().getBoundingClientRect().top }))
|
||||
|
||||
// The top left corner was under the pointer, so it stays in the corner
|
||||
await vi.waitFor(() => {
|
||||
expect(viewBox()).toEqual([0, 0, 15, 5])
|
||||
})
|
||||
})
|
||||
|
||||
it('should go back to the whole map on reset', async () => {
|
||||
const map = zoomable()
|
||||
wheel(-500)
|
||||
|
||||
map.reset()
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(viewBox()).toEqual([0, 0, 30, 10])
|
||||
})
|
||||
})
|
||||
|
||||
it('should drag the map with a pointer, but never out of sight', () => {
|
||||
zoomable({ zoomMax: 2 })
|
||||
wheel(-5000)
|
||||
expect(viewBox()).toEqual([7.5, 2.5, 15, 5])
|
||||
|
||||
// 300px show 15 units, so 40px is 2 units
|
||||
pointer('pointerdown', 100, 50)
|
||||
pointer('pointermove', 60, 50)
|
||||
expect(viewBox()).toEqual([9.5, 2.5, 15, 5])
|
||||
expect(el().classList.contains('vector-map-dragging')).toBe(true)
|
||||
|
||||
pointer('pointermove', -1000, 50)
|
||||
expect(viewBox()).toEqual([15, 2.5, 15, 5])
|
||||
|
||||
pointer('pointerup', -1000, 50)
|
||||
expect(el().classList.contains('vector-map-dragging')).toBe(false)
|
||||
|
||||
pointer('pointermove', 0, 50)
|
||||
expect(viewBox()).toEqual([15, 2.5, 15, 5])
|
||||
})
|
||||
|
||||
it('should not drag a map that cannot be zoomed', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" style="width: 300px"></div>'
|
||||
new VectorMap(el(), { map: squares })
|
||||
|
||||
pointer('pointerdown', 100, 50)
|
||||
pointer('pointermove', 60, 50)
|
||||
|
||||
expect(el().classList.contains('vector-map-dragging')).toBe(false)
|
||||
})
|
||||
|
||||
it('should zoom when two pointers move apart', () => {
|
||||
zoomable()
|
||||
const rect = el().getBoundingClientRect()
|
||||
const y = rect.top + 50
|
||||
|
||||
pointer('pointerdown', rect.left + 140, y, 1)
|
||||
pointer('pointerdown', rect.left + 160, y, 2)
|
||||
pointer('pointermove', rect.left + 180, y, 2)
|
||||
|
||||
// 20px between the fingers became 40px
|
||||
expect(viewBox()[2]).toBe(15)
|
||||
})
|
||||
|
||||
it('should start from the whole map again after render()', () => {
|
||||
const map = zoomable()
|
||||
wheel(-500)
|
||||
|
||||
map.render()
|
||||
|
||||
expect(viewBox()).toEqual([0, 0, 30, 10])
|
||||
expect(el().querySelectorAll('.vector-map-controls').length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
it('should replace the previous drawing', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
const map = new VectorMap(el(), { map: squares })
|
||||
map.render()
|
||||
|
||||
expect(el().querySelectorAll('svg').length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should remove the drawing', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map"></div>'
|
||||
|
||||
const map = new VectorMap(el(), { map: squares, zoom: true })
|
||||
region('AA').dispatchEvent(new PointerEvent('pointermove', { bubbles: true }))
|
||||
map.dispose()
|
||||
|
||||
expect(el().querySelector('svg')).toBeNull()
|
||||
expect(el().querySelector('.vector-map-tooltip')).toBeNull()
|
||||
expect(el().querySelector('.vector-map-controls')).toBeNull()
|
||||
expect(el().classList.contains('vector-map-zoomable')).toBe(false)
|
||||
expect(VectorMap.getInstance(el())).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('world map', () => {
|
||||
it('should have countries by their ISO code, each with a shape', () => {
|
||||
expect(Object.keys(world.regions).length).toBeGreaterThan(170)
|
||||
expect(world.regions.PL.name).toBe('Poland')
|
||||
expect(world.regions.FR.name).toBe('France')
|
||||
expect(world.regions.AQ).toBeUndefined()
|
||||
|
||||
for (const [code, { path }] of Object.entries(world.regions)) {
|
||||
expect(code).toMatch(/^[A-Z]{2}$/)
|
||||
expect(path).toMatch(/^M[-\d. ]+l[-\d. ]+z/)
|
||||
}
|
||||
})
|
||||
|
||||
it('should draw inside its viewBox', () => {
|
||||
fixtureEl.innerHTML = '<div class="vector-map" style="width: 500px"></div>'
|
||||
|
||||
new VectorMap(el(), { map: world })
|
||||
|
||||
const svg = el().querySelector('svg')!
|
||||
// The shapes are rounded to one decimal place
|
||||
const box = svg.getBBox()
|
||||
expect(box.x).toBeGreaterThanOrEqual(-0.05)
|
||||
expect(box.y).toBeGreaterThanOrEqual(-0.05)
|
||||
expect(box.x + box.width).toBeLessThanOrEqual(world.width + 0.05)
|
||||
expect(box.y + box.height).toBeLessThanOrEqual(world.height + 0.05)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -55,6 +55,10 @@
|
||||
"js": ["dist/js/tom-select.base.min.js"],
|
||||
"css": ["dist/css/tom-select.bootstrap5.min.css"]
|
||||
},
|
||||
"vector-map": {
|
||||
"addon": "tabler-vector-map",
|
||||
"maps": ["world"]
|
||||
},
|
||||
"jsvectormap": {
|
||||
"npm": "jsvectormap",
|
||||
"js": ["dist/jsvectormap.min.js", "dist/maps/world.js", "dist/maps/world-merc.js"],
|
||||
|
||||
+29
-2
@@ -17,12 +17,15 @@
|
||||
"lint:scss:vars": "find-unused-sass-variables scss/ --ignoreFiles scss/_variables-deprecated.scss",
|
||||
"js": "pnpm run js:build && pnpm run js:min && pnpm run js:types",
|
||||
"js:types": "tsc -p tsconfig.types.json",
|
||||
"js:build": "concurrently \"pnpm run js:build:standalone\" \"pnpm run js:build:theme\"",
|
||||
"js:build": "concurrently \"pnpm run js:build:standalone\" \"pnpm run js:build:theme\" \"pnpm run js:build:vector-map\" \"pnpm run js:build:maps\"",
|
||||
"js:build:vector-map": "cross-env BASE_NAME=tabler-vector-map vite build --config .build/vite.config.mts",
|
||||
"js:build:maps": "cross-env BASE_NAME=maps/world vite build --config .build/vite.config.mts",
|
||||
"js:build:theme": "cross-env BASE_NAME=tabler-theme vite build --config .build/vite.config.mts",
|
||||
"js:build:standalone": "cross-env BASE_NAME=tabler vite build --config .build/vite.config.mts",
|
||||
"js:min": "concurrently \"pnpm run js:min:standalone\" \"pnpm run js:min:theme\"",
|
||||
"js:min": "concurrently \"pnpm run js:min:standalone\" \"pnpm run js:min:theme\" \"pnpm run js:min:vector-map\"",
|
||||
"js:min:standalone": "concurrently \"terser dist/js/tabler.js --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler.js.map,filename=dist/js/tabler.min.js.map,url=tabler.min.js.map\\\" -o dist/js/tabler.min.js\" \"terser dist/js/tabler.esm.js --module --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler.esm.js.map,filename=dist/js/tabler.esm.min.js.map,url=tabler.esm.min.js.map\\\" -o dist/js/tabler.esm.min.js\"",
|
||||
"js:min:theme": "concurrently \"terser dist/js/tabler-theme.js --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler-theme.js.map,filename=dist/js/tabler-theme.min.js.map,url=tabler-theme.min.js.map\\\" -o dist/js/tabler-theme.min.js\" \"terser dist/js/tabler-theme.esm.js --module --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler-theme.esm.js.map,filename=dist/js/tabler-theme.esm.min.js.map,url=tabler-theme.esm.min.js.map\\\" -o dist/js/tabler-theme.esm.min.js\"",
|
||||
"js:min:vector-map": "concurrently \"terser dist/js/tabler-vector-map.js --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler-vector-map.js.map,filename=dist/js/tabler-vector-map.min.js.map,url=tabler-vector-map.min.js.map\\\" -o dist/js/tabler-vector-map.min.js\" \"terser dist/js/tabler-vector-map.esm.js --module --compress passes=2,ecma=2020 --ecma 2020 --mangle --source-map \\\"content=dist/js/tabler-vector-map.esm.js.map,filename=dist/js/tabler-vector-map.esm.min.js.map,url=tabler-vector-map.esm.min.js.map\\\" -o dist/js/tabler-vector-map.esm.min.js\"",
|
||||
"copy": "concurrently \"pnpm run copy:img\" \"pnpm run copy:libs\"",
|
||||
"copy:img": "tsx .build/copy-img.ts",
|
||||
"copy:libs": "tsx .build/copy-libs.ts",
|
||||
@@ -241,6 +244,30 @@
|
||||
{
|
||||
"path": "./dist/js/tabler-theme.esm.min.js",
|
||||
"maxSize": "800 B"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler-vector-map.js",
|
||||
"maxSize": "11.5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler-vector-map.esm.js",
|
||||
"maxSize": "11.5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler-vector-map.min.js",
|
||||
"maxSize": "8 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler-vector-map.esm.min.js",
|
||||
"maxSize": "8 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/maps/world.js",
|
||||
"maxSize": "28 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/maps/world.esm.js",
|
||||
"maxSize": "28 kB"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
@forward 'ui/type';
|
||||
@forward 'ui/charts';
|
||||
@forward 'ui/sparkline';
|
||||
@forward 'ui/vector-map';
|
||||
@forward 'ui/offcanvas';
|
||||
@forward 'ui/chat';
|
||||
@forward 'ui/signature';
|
||||
|
||||
@@ -2265,6 +2265,23 @@ $sparkline-sizes: (
|
||||
) !default;
|
||||
// scss-docs-end sparkline-variables
|
||||
|
||||
// scss-docs-start vector-map-variables
|
||||
$vector-map-color: var(--primary) !default;
|
||||
$vector-map-region-fill: var(--border-color) !default;
|
||||
$vector-map-region-stroke: var(--bg-surface) !default;
|
||||
$vector-map-region-stroke-width: 1px !default;
|
||||
$vector-map-value-min: 0.1 !default;
|
||||
$vector-map-marker-size: 0.5rem !default;
|
||||
$vector-map-marker-color: var(--vector-map-color) !default;
|
||||
$vector-map-marker-halo-color: var(--bg-surface) !default;
|
||||
$vector-map-marker-halo-width: 2px !default;
|
||||
$vector-map-line-color: var(--secondary) !default;
|
||||
$vector-map-line-width: 1px !default;
|
||||
$vector-map-line-dash: 4px !default;
|
||||
$vector-map-line-duration: 1s !default;
|
||||
$vector-map-controls-offset: var(--spacer-2) !default;
|
||||
// scss-docs-end vector-map-variables
|
||||
|
||||
// Patterns
|
||||
$pattern-size: 0.5rem !default;
|
||||
$pattern-size-sm: 0.25rem !default;
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Vector map (inline SVG, rendered by js/src/vector-map.ts)
|
||||
@use '../config' as *;
|
||||
|
||||
@keyframes vector-map-line {
|
||||
to {
|
||||
stroke-dashoffset: calc(var(--vector-map-line-dash) * -2);
|
||||
}
|
||||
}
|
||||
|
||||
.vector-map {
|
||||
// scss-docs-start vector-map-css-vars
|
||||
--vector-map-color: #{$vector-map-color};
|
||||
--vector-map-region-fill: #{$vector-map-region-fill};
|
||||
--vector-map-region-stroke: #{$vector-map-region-stroke};
|
||||
--vector-map-region-stroke-width: #{$vector-map-region-stroke-width};
|
||||
--vector-map-value-min: #{$vector-map-value-min};
|
||||
--vector-map-marker-size: #{$vector-map-marker-size};
|
||||
--vector-map-marker-color: #{$vector-map-marker-color};
|
||||
--vector-map-marker-halo-color: #{$vector-map-marker-halo-color};
|
||||
--vector-map-marker-halo-width: #{$vector-map-marker-halo-width};
|
||||
--vector-map-line-color: #{$vector-map-line-color};
|
||||
--vector-map-line-width: #{$vector-map-line-width};
|
||||
--vector-map-line-dash: #{$vector-map-line-dash};
|
||||
--vector-map-line-duration: #{$vector-map-line-duration};
|
||||
--vector-map-controls-offset: #{$vector-map-controls-offset};
|
||||
// scss-docs-end vector-map-css-vars
|
||||
|
||||
position: relative;
|
||||
display: block;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.vector-map-svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vector-map-region {
|
||||
fill: var(--vector-map-region-fill);
|
||||
stroke: var(--vector-map-region-stroke);
|
||||
stroke-linejoin: round;
|
||||
stroke-width: var(--vector-map-region-stroke-width);
|
||||
// The border keeps its width however large the map is drawn
|
||||
vector-effect: non-scaling-stroke;
|
||||
|
||||
// `--vector-map-value` is the 0 to 1 share set by the plugin. The lowest
|
||||
// value still gets `--vector-map-value-min` of the colour, so it stands out
|
||||
// from a region with no value at all.
|
||||
&[data-value] {
|
||||
fill: color-mix(in oklab, var(--vector-map-color) calc((var(--vector-map-value-min) + var(--vector-map-value, 1) * (1 - var(--vector-map-value-min))) * 100%), var(--vector-map-region-fill));
|
||||
}
|
||||
}
|
||||
|
||||
// Dashes that move from the start of the line to its end
|
||||
.vector-map-line {
|
||||
fill: none;
|
||||
stroke: var(--vector-map-line-color);
|
||||
stroke-dasharray: var(--vector-map-line-dash);
|
||||
stroke-width: var(--vector-map-line-width);
|
||||
vector-effect: non-scaling-stroke;
|
||||
animation: vector-map-line var(--vector-map-line-duration) linear infinite;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Both parts of a marker are a line of no length: its round caps make a dot
|
||||
// as wide as the stroke, and the non-scaling stroke keeps it that wide in
|
||||
// pixels however large the map is drawn.
|
||||
.vector-map-marker-halo,
|
||||
.vector-map-marker-dot {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.vector-map-marker-halo {
|
||||
stroke: var(--vector-map-marker-halo-color);
|
||||
stroke-width: calc(var(--vector-map-marker-size) + var(--vector-map-marker-halo-width) * 2);
|
||||
}
|
||||
|
||||
.vector-map-marker-dot {
|
||||
stroke: var(--vector-map-marker-color);
|
||||
stroke-width: var(--vector-map-marker-size);
|
||||
}
|
||||
|
||||
// Follows the pointer, placed by the plugin with `left` and `top`
|
||||
.vector-map-tooltip {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
padding: $tooltip-padding-y $tooltip-padding-x;
|
||||
font-size: $tooltip-font-size;
|
||||
line-height: $line-height-base;
|
||||
color: $tooltip-color;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
background: $tooltip-bg;
|
||||
@include border-radius($tooltip-border-radius);
|
||||
|
||||
/* rtl:ignore */
|
||||
transform: translate(-50%, calc(-100% - #{$spacer-2}));
|
||||
}
|
||||
|
||||
// A map that can be magnified takes the drags and pinches for itself, so the
|
||||
// page does not scroll or zoom under the finger.
|
||||
.vector-map-zoomable .vector-map-svg {
|
||||
touch-action: none;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vector-map-dragging .vector-map-svg {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.vector-map-controls {
|
||||
position: absolute;
|
||||
inset-block-start: var(--vector-map-controls-offset);
|
||||
inset-inline-end: var(--vector-map-controls-offset);
|
||||
z-index: 1;
|
||||
|
||||
.icon {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 2;
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,5 @@
|
||||
"rootDir": "js",
|
||||
"outDir": "dist/types"
|
||||
},
|
||||
"include": ["js/tabler.ts", "js/tabler-theme.ts", "js/src/**/*"]
|
||||
"include": ["js/tabler.ts", "js/tabler-theme.ts", "js/tabler-vector-map.ts", "js/maps/*.ts", "js/src/**/*"]
|
||||
}
|
||||
@@ -1,99 +1,240 @@
|
||||
---
|
||||
title: Vector map
|
||||
docs-libs: [jsvectormap]
|
||||
description: Interactive SVG maps with jsVectorMap, with a world map, markers and lines.
|
||||
summary: A vector map draws countries as SVG paths that can be styled and clicked. Tabler themes jsVectorMap to match the interface.
|
||||
related: [/ui/components/map]
|
||||
docs-libs: [vector-map]
|
||||
description: Draw a world map as inline SVG from data attributes. Shade countries by value, add markers and lines, show tooltips and let users zoom, with no map library.
|
||||
summary: A vector map draws countries as SVG paths, shaded by a value, with markers, lines, tooltips and zoom. Tabler renders it from data attributes, with no map library and no map provider.
|
||||
added-in: '1.7.0'
|
||||
related: [/ui/components/map, /ui/components/sparkline]
|
||||
source: core/scss/ui/_vector-map.scss
|
||||
css-vars: vector-map
|
||||
classnames:
|
||||
component:
|
||||
- class: vector-map
|
||||
desc: Box that holds the map, as wide as its parent
|
||||
part:
|
||||
- class: vector-map-svg
|
||||
desc: The rendered SVG, added by JavaScript
|
||||
- class: vector-map-region
|
||||
desc: One country or region, a path with a data-region code
|
||||
- class: vector-map-marker
|
||||
desc: Group that holds the two parts of a marker
|
||||
- class: vector-map-marker-dot
|
||||
desc: The colored dot of a marker
|
||||
- class: vector-map-marker-halo
|
||||
desc: Ring around the dot, in the surface color
|
||||
- class: vector-map-line
|
||||
desc: Dashed curve between two markers
|
||||
- class: vector-map-tooltip
|
||||
desc: Label that follows the pointer
|
||||
- class: vector-map-controls
|
||||
desc: Zoom buttons in the corner of the map
|
||||
---
|
||||
|
||||
import Example from '@components/Example.astro'
|
||||
import MapVector from '@shared/components/demo/MapVector.astro'
|
||||
import { Code } from 'astro:components'
|
||||
import { site } from '@shared/lib/site.ts'
|
||||
import TabsPackage from '@components/TabsPackage.astro'
|
||||
import Example from '@components/Example.astro';
|
||||
import CodeDocs from '@components/CodeDocs.astro';
|
||||
import { Code } from 'astro:components';
|
||||
import { site } from '@shared/lib/site.ts';
|
||||
|
||||
## Overview
|
||||
|
||||
A vector map draws countries and regions as SVG paths, so it stays sharp at any size and every region can be styled or clicked. Tabler uses [jsVectorMap](https://jvm-docs.vercel.app/), and themes it to match the rest of the interface.
|
||||
A vector map draws countries as SVG paths, so it stays sharp at any size. Use it to show a number per country, a few places, or routes between them.
|
||||
|
||||
The library ships the map data separately from the code. Load the map you need - the world map here - or the build will render an empty box.
|
||||
The component is Tabler's own. It needs no map library, no API key and no network request. The country shapes come from [Natural Earth](https://www.naturalearthdata.com/), which is public domain.
|
||||
|
||||
## Installation
|
||||
|
||||
Install jsVectorMap with npm:
|
||||
|
||||
<TabsPackage name="jsvectormap" />
|
||||
|
||||
Or include it from a CDN. You need three files: the stylesheet, the library, and the map data.
|
||||
The vector map is not part of `tabler.js`, so pages without a map do not load it. Add two files after `tabler.js`: the component and the map data.
|
||||
|
||||
<Code
|
||||
lang="html"
|
||||
code={`<link rel="stylesheet" href="${site.cdnUrl}/dist/libs/jsvectormap/dist/jsvectormap.css" />
|
||||
<script src="${site.cdnUrl}/dist/libs/jsvectormap/dist/jsvectormap.min.js"></script>
|
||||
<script src="${site.cdnUrl}/dist/libs/jsvectormap/dist/maps/world.js"></script>`}
|
||||
code={`<script src="${site.cdnUrl}/dist/js/tabler.min.js"></script>
|
||||
<script src="${site.cdnUrl}/dist/js/tabler-vector-map.min.js"></script>
|
||||
<script src="${site.cdnUrl}/dist/js/maps/world.js"></script>`}
|
||||
/>
|
||||
|
||||
Use `maps/world-merc.js` instead for the Mercator projection.
|
||||
The styles are part of `tabler.css`.
|
||||
|
||||
## Usage
|
||||
|
||||
Add an empty element with a height, then point jsVectorMap at it. The `map` option has to match the map file you loaded.
|
||||
|
||||
```html
|
||||
<div id="map-world" style="height: 20rem"></div>
|
||||
```
|
||||
With a bundler, import the component and the map, then register the map under a name:
|
||||
|
||||
```js
|
||||
new jsVectorMap({
|
||||
selector: '#map-world',
|
||||
map: 'world',
|
||||
});
|
||||
import { VectorMap } from '@tabler/core/dist/js/tabler-vector-map.esm.js';
|
||||
import { world } from '@tabler/core/dist/js/maps/world.esm.js';
|
||||
|
||||
VectorMap.addMap('world', world);
|
||||
```
|
||||
|
||||
The map sizes itself from its container, so the container needs a height before the map is created. Call `updateSize()` on resize to keep it in step:
|
||||
## Default markup
|
||||
|
||||
```js
|
||||
const map = new jsVectorMap({ selector: '#map-world', map: 'world' });
|
||||
Add `class="vector-map"` and `data-bs-toggle="vector-map"` to an empty element. Tabler draws the map on page load. The map is as wide as its parent and keeps its own proportions, so it needs no height.
|
||||
|
||||
window.addEventListener('resize', () => map.updateSize());
|
||||
```
|
||||
|
||||
## Default map
|
||||
|
||||
The code for a world map with the default options:
|
||||
|
||||
<Example codeOnly>
|
||||
<MapVector mapId="empty" />
|
||||
</Example>
|
||||
|
||||
## Sample demo
|
||||
|
||||
The same map rendered on the page:
|
||||
Set `data-bs-label` to a short text that says what the map shows. Without it the map is hidden from screen readers.
|
||||
|
||||
<Example>
|
||||
<MapVector mapId="world" />
|
||||
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="World map"></div>
|
||||
</Example>
|
||||
|
||||
## Values
|
||||
|
||||
Pass a number per country in `data-bs-values`, as a JSON object. The keys are ISO 3166-1 alpha-2 codes, such as `PL` or `US`. The country with the highest value gets the full color and the others get a lighter shade.
|
||||
|
||||
<Example>
|
||||
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Visits by country" data-bs-values='{"US": 100, "CA": 62, "BR": 48, "GB": 71, "DE": 80, "FR": 66, "PL": 54, "IN": 90, "CN": 85, "JP": 59, "AU": 40, "ZA": 22, "EG": 18, "MX": 35, "AR": 27}'></div>
|
||||
</Example>
|
||||
|
||||
The shades are spread between the lowest and the highest value. Set `data-bs-min` and `data-bs-max` to use a fixed range instead, for example `0` and `100` for a percentage.
|
||||
|
||||
### Color
|
||||
|
||||
The map uses the primary color. Set `--tblr-vector-map-color` to change it.
|
||||
|
||||
<Example>
|
||||
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Growth by country" data-bs-min="0" data-bs-values='{"US": 4, "CA": 7, "BR": 9, "DE": 3, "PL": 8, "IN": 10, "CN": 6, "AU": 5}' style="--tblr-vector-map-color: var(--tblr-green)"></div>
|
||||
</Example>
|
||||
|
||||
## Markers
|
||||
|
||||
Markers point out single locations, such as offices or customers:
|
||||
Markers point at single places, such as offices or customers. Pass them in `data-bs-markers` as a JSON array. Each marker has a `lat`, a `lng` and an optional `name`. A marker keeps its size when the map is resized or zoomed.
|
||||
|
||||
<Example>
|
||||
<MapVector mapId="world-markers" />
|
||||
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Our offices" data-bs-markers='[{"name": "Warsaw", "lat": 52.23, "lng": 21.01}, {"name": "New York", "lat": 40.71, "lng": -74.01}, {"name": "Tokyo", "lat": 35.68, "lng": 139.69}, {"name": "Sydney", "lat": -33.87, "lng": 151.21}]' style="--tblr-vector-map-color: var(--tblr-red)"></div>
|
||||
</Example>
|
||||
|
||||
## Lines
|
||||
|
||||
Lines connect locations, for a route or a link between two points:
|
||||
Lines join two markers, for a route or a connection. Pass them in `data-bs-lines` as a JSON array. `from` and `to` are the names of two markers. The dashes move from the start of the line to its end, and stand still for users who ask for reduced motion.
|
||||
|
||||
<Example>
|
||||
<MapVector mapId="world-lines" />
|
||||
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Flights from Warsaw" data-bs-markers='[{"name": "Warsaw", "lat": 52.23, "lng": 21.01}, {"name": "New York", "lat": 40.71, "lng": -74.01}, {"name": "Tokyo", "lat": 35.68, "lng": 139.69}, {"name": "Cape Town", "lat": -33.92, "lng": 18.42}]' data-bs-lines='[{"from": "Warsaw", "to": "New York"}, {"from": "Warsaw", "to": "Tokyo"}, {"from": "Warsaw", "to": "Cape Town"}]'></div>
|
||||
</Example>
|
||||
|
||||
## Zoom
|
||||
|
||||
Add `data-bs-zoom="true"` to let users look closer. The map gets two buttons, and reacts to the mouse wheel, a double click, a pinch and a drag.
|
||||
|
||||
<Example>
|
||||
<div class="vector-map" data-bs-toggle="vector-map" data-bs-zoom="true" data-bs-label="Visits in Europe" data-bs-values='{"PL": 8, "DE": 6, "FR": 5, "ES": 3, "IT": 4, "GB": 7, "SE": 2, "UA": 3, "RO": 2, "CZ": 5}'></div>
|
||||
</Example>
|
||||
|
||||
While the pointer is over a zoomable map, the wheel zooms the map and does not scroll the page. Set `data-bs-zoom-on-scroll="false"` to keep the wheel for the page. On a touch screen a finger on the map moves the map, not the page, so leave some room around a large map.
|
||||
|
||||
## Tooltip
|
||||
|
||||
A tooltip shows the name of the country or marker under the pointer, with the value when the country has one. Set `data-bs-tooltip="false"` to turn it off.
|
||||
|
||||
Pass a function from JavaScript to write your own text. It gets the item under the pointer and returns a string. The string is shown as plain text. Return an empty string to show nothing.
|
||||
|
||||
```js
|
||||
new tabler.VectorMap('#visits', {
|
||||
values: { PL: 54, DE: 80 },
|
||||
tooltip: (item) => (item.type === 'region' && item.value !== undefined ? `${item.name}: ${item.value}%` : item.name),
|
||||
});
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
Set options with data attributes or pass them to the constructor. For a data attribute, add `data-bs-` to the name and write it in kebab case, for example `data-bs-zoom-max`.
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `map` | string, object | `'world'` | Name of a loaded map, or the map data itself. |
|
||||
| `values` | object | `{}` | A number per region code. |
|
||||
| `min` | number, null | `null` | Value that gets the lightest shade. The lowest value when `null`. |
|
||||
| `max` | number, null | `null` | Value that gets the full color. The highest value when `null`. |
|
||||
| `markers` | array | `[]` | Places as `{ name, lat, lng }`. |
|
||||
| `lines` | array | `[]` | Connections as `{ from, to }`, by marker name. |
|
||||
| `tooltip` | boolean, function | `true` | Shows a tooltip, or builds its text. |
|
||||
| `zoom` | boolean | `false` | Lets users zoom and drag the map. |
|
||||
| `zoomMax` | number | `8` | The largest magnification. |
|
||||
| `zoomOnScroll` | boolean | `true` | Zooms with the mouse wheel. Needs `zoom`. |
|
||||
| `zoomInLabel` | string | `'Zoom in'` | Accessible name of the zoom in button. |
|
||||
| `zoomOutLabel` | string | `'Zoom out'` | Accessible name of the zoom out button. |
|
||||
| `label` | string, null | `null` | Accessible name of the map. |
|
||||
|
||||
## JavaScript
|
||||
|
||||
Tabler wraps the element in a `VectorMap` component that works like the Bootstrap components: it is created once per element and stored on it. When the page is loaded, Tabler creates one for every element with `data-bs-toggle="vector-map"`. This is the code that runs:
|
||||
|
||||
<CodeDocs name="vector-map-init" file="core/js/src/vector-map.ts" />
|
||||
|
||||
Create the component yourself for an element added later, or to pass the data from code:
|
||||
|
||||
```js
|
||||
const visits = tabler.VectorMap.getOrCreateInstance(document.getElementById('visits'), {
|
||||
values: { PL: 54, DE: 80, FR: 66 },
|
||||
});
|
||||
```
|
||||
|
||||
Call `update()` with new values to shade the map again. The map is not drawn again, so this is cheap enough for live data:
|
||||
|
||||
```js
|
||||
visits.update({ PL: 61, DE: 77, FR: 70 });
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `update(values)` | Shades the regions with new values. Fires `updated.bs.vector-map`. |
|
||||
| `render()` | Reads the attributes again and draws the map from the start. Fires `rendered.bs.vector-map`. |
|
||||
| `zoomIn()` | Zooms in one step, around the middle of the view. |
|
||||
| `zoomOut()` | Zooms out one step. |
|
||||
| `reset()` | Goes back to the whole map. |
|
||||
| `dispose()` | Removes the map, the buttons, the tooltip and the component from the element. |
|
||||
| `VectorMap.addMap(name, data)` | Static. Registers a map under a name. |
|
||||
| `getInstance(element)` | Static. Returns the component for the element, or `null`. |
|
||||
| `getOrCreateInstance(element, config)` | Static. Returns the component for the element and creates it when needed. |
|
||||
|
||||
| Event | Description |
|
||||
| --- | --- |
|
||||
| `rendered.bs.vector-map` | Fired on the element after every render. |
|
||||
| `updated.bs.vector-map` | Fired on the element after `update()`. |
|
||||
|
||||
## Styling a single country
|
||||
|
||||
Every country is a path with its code in `data-region`, so one rule can style it:
|
||||
|
||||
```css
|
||||
.vector-map [data-region='PL'] {
|
||||
fill: var(--tblr-red);
|
||||
}
|
||||
```
|
||||
|
||||
## Custom properties
|
||||
|
||||
The map reads its colors and sizes from custom properties on the element. Change them for one map with a `style` attribute, or for a section with a class.
|
||||
|
||||
| Property | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `--tblr-vector-map-color` | `var(--tblr-primary)` | Color of the highest value, and of the markers. |
|
||||
| `--tblr-vector-map-region-fill` | `var(--tblr-border-color)` | Fill of a country with no value. |
|
||||
| `--tblr-vector-map-region-stroke` | `var(--tblr-bg-surface)` | Border between countries. |
|
||||
| `--tblr-vector-map-region-stroke-width` | `1px` | Width of the border. |
|
||||
| `--tblr-vector-map-value-min` | `0.1` | Share of the color the lowest value gets, from 0 to 1. |
|
||||
| `--tblr-vector-map-marker-size` | `0.5rem` | Diameter of a marker. |
|
||||
| `--tblr-vector-map-marker-color` | `var(--tblr-vector-map-color)` | Color of a marker. |
|
||||
| `--tblr-vector-map-marker-halo-color` | `var(--tblr-bg-surface)` | Color of the ring around a marker. |
|
||||
| `--tblr-vector-map-marker-halo-width` | `2px` | Width of the ring. |
|
||||
| `--tblr-vector-map-line-color` | `var(--tblr-secondary)` | Color of a line. |
|
||||
| `--tblr-vector-map-line-width` | `1px` | Width of a line. |
|
||||
| `--tblr-vector-map-line-dash` | `4px` | Length of a dash and of the gap after it. |
|
||||
| `--tblr-vector-map-line-duration` | `1s` | Time one dash takes to move to the place of the next. |
|
||||
| `--tblr-vector-map-controls-offset` | `var(--tblr-spacer-2)` | Distance of the zoom buttons from the corner. |
|
||||
|
||||
## The world map
|
||||
|
||||
The world map has 176 countries in the Miller projection, without Antarctica. It is made for an overview, so very small countries, such as Singapore or Malta, are not on it. Values for a code that is not on the map are skipped.
|
||||
|
||||
Three territories have no ISO code. The map uses `XK` for Kosovo, `XC` for Northern Cyprus and `XS` for Somaliland. Borders follow the default view of Natural Earth.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- A map is a picture built from paths, and a screen reader reads them as a list of shapes. Give the container `role="img"` and an `aria-label` that says what it shows.
|
||||
- The data behind the map has to be reachable another way. A table of the same values, next to or below the map, serves screen reader users and anyone printing the page.
|
||||
- Hovering a region is mouse-only. Do not put anything there that is not also available in the text version.
|
||||
- Colour scales are hard to read for many users. Add a legend with the actual numbers rather than color alone.
|
||||
- Do not make every region focusable. A world map would be hundreds of tab stops.
|
||||
- Set `data-bs-label`. The map is then an image with that name. The countries themselves are not read out one by one.
|
||||
- The data behind the map has to be reachable another way. Put a table of the same values next to or below the map. It serves screen reader users and anyone who prints the page.
|
||||
- The tooltip is for the pointer only. Do not put anything there that is not in the text version too.
|
||||
- Shades of one color are hard to tell apart for many users. Add a legend with the real numbers.
|
||||
- The zoom buttons work with the keyboard and have accessible names. Change the names with `data-bs-zoom-in-label` and `data-bs-zoom-out-label` when the page is not in English.
|
||||
|
||||
## Variables
|
||||
|
||||
<CodeDocs name="vector-map-variables" file="core/scss/_variables.scss" />
|
||||
|
||||
## Migration notes
|
||||
|
||||
Before 1.7 this page used [jsVectorMap](https://jvm-docs.vercel.app/). Its files are still in `dist/libs/jsvectormap` and its styles are still in `tabler-vendors.css`, so a page that uses it keeps working. The demo and the docs now use the component above.
|
||||
@@ -142,7 +142,7 @@ const editFilePath =
|
||||
|
||||
// docs-libs for this page. In dev, file names stay unchanged
|
||||
// (e.g. dist/plyr.min.js keeps .min).
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[] };
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[]; addon?: string; maps?: string[] };
|
||||
const libEntries = Object.entries(libs as Record<string, Lib>);
|
||||
const libUrl = (lib: Lib, file: string) =>
|
||||
isExternal(file)
|
||||
@@ -151,6 +151,8 @@ const libUrl = (lib: Lib, file: string) =>
|
||||
const docsLibCss = libEntries
|
||||
.filter(([name]) => docsLibs.includes(name))
|
||||
.flatMap(([, lib]) => (lib.css ?? []).map((file) => libUrl(lib, file)));
|
||||
const docsLibAddons = libEntries.filter(([name]) => docsLibs.includes(name)).flatMap(([, lib]) => (lib.addon ? [lib.addon] : []));
|
||||
const docsLibMaps = libEntries.filter(([name]) => docsLibs.includes(name)).flatMap(([, lib]) => lib.maps ?? []);
|
||||
const docsLibJs = libEntries
|
||||
.filter(([name]) => docsLibs.includes(name))
|
||||
.flatMap(([, lib]) => (lib.js ?? []).map((file) => libUrl(lib, file)));
|
||||
@@ -521,6 +523,8 @@ const relatedPages = await Promise.all(
|
||||
|
||||
<!-- BEGIN GLOBAL MANDATORY SCRIPTS -->
|
||||
<script is:inline src="/dist/js/tabler.js" defer></script>
|
||||
{docsLibAddons.map((addon) => <script is:inline src={`/dist/js/${addon}.js`} defer />)}
|
||||
{docsLibMaps.map((map) => <script is:inline src={`/dist/js/maps/${map}.js`} defer />)}
|
||||
<!-- END GLOBAL MANDATORY SCRIPTS -->
|
||||
|
||||
<PageScripts />
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"html-diff:baseline": "tsx .build/html-diff.ts baseline",
|
||||
"generate:tokens": "tsx .build/generate-tokens.ts",
|
||||
"generate:sri": "tsx .build/generate-sri.ts",
|
||||
"generate:maps": "tsx .build/generate-maps.ts",
|
||||
"import:icons": "tsx .build/import-icons.ts",
|
||||
"import:illustrations": "tsx .build/import-illustrations.ts",
|
||||
"import:payments": "tsx .build/import-payments.ts",
|
||||
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pretitle="Overview" pageMenu="dashboards.default" pageLibs={['apexcharts', 'jsvectormap']}>
|
||||
<DefaultLayout title="Dashboard" pretitle="Overview" pageMenu="dashboards.default" pageLibs={['apexcharts', 'vector-map']}>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Boxed layout" pretitle="Overview" pageMenu="layout.boxed" pageLibs={['apexcharts', 'jsvectormap']} layout="boxed">
|
||||
<DefaultLayout title="Dashboard" pageHeader="Boxed layout" pretitle="Overview" pageMenu="layout.boxed" pageLibs={['apexcharts', 'vector-map']} layout="boxed">
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Condensed layout" pretitle="Overview" pageMenu="layout.condensed" pageLibs={['apexcharts', 'jsvectormap']} navbarCondensed>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Condensed layout" pretitle="Overview" pageMenu="layout.condensed" pageLibs={['apexcharts', 'vector-map']} navbarCondensed>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Fluid vertical layout" pretitle="Overview" pageMenu="layout.fluid-vertical" pageLibs={['apexcharts', 'jsvectormap']} layout="fluid" navbarPosition="vertical">
|
||||
<DefaultLayout title="Dashboard" pageHeader="Fluid vertical layout" pretitle="Overview" pageMenu="layout.fluid-vertical" pageLibs={['apexcharts', 'vector-map']} layout="fluid" navbarPosition="vertical">
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Fluid layout" pretitle="Overview" pageMenu="layout.fluid" pageLibs={['apexcharts', 'jsvectormap']} layout="fluid">
|
||||
<DefaultLayout title="Dashboard" pageHeader="Fluid layout" pretitle="Overview" pageMenu="layout.fluid" pageLibs={['apexcharts', 'vector-map']} layout="fluid">
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Folded hover layout" pretitle="Overview" pageMenu="layout.folded-hover" pageLibs={['apexcharts', 'jsvectormap']} navbarPosition="vertical" sidebarMode="folded-hover">
|
||||
<DefaultLayout title="Dashboard" pageHeader="Folded hover layout" pretitle="Overview" pageMenu="layout.folded-hover" pageLibs={['apexcharts', 'vector-map']} navbarPosition="vertical" sidebarMode="folded-hover">
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Folded sidebar layout" pretitle="Overview" pageMenu="layout.folded" pageLibs={['apexcharts', 'jsvectormap']} navbarPosition="vertical" sidebarMode="folded">
|
||||
<DefaultLayout title="Dashboard" pageHeader="Folded sidebar layout" pretitle="Overview" pageMenu="layout.folded" pageLibs={['apexcharts', 'vector-map']} navbarPosition="vertical" sidebarMode="folded">
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Horizontal layout" pretitle="Overview" pageMenu="layout.horizontal" pageLibs={['apexcharts', 'jsvectormap']}>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Horizontal layout" pretitle="Overview" pageMenu="layout.horizontal" pageLibs={['apexcharts', 'vector-map']}>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Navbar dark" pretitle="Overview" pageMenu="layout.navbar-dark" pageLibs={['apexcharts', 'jsvectormap']} navbarDark>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Navbar dark" pretitle="Overview" pageMenu="layout.navbar-dark" pageLibs={['apexcharts', 'vector-map']} navbarDark>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Navbar floating" pretitle="Overview" pageMenu="layout.navbar-floating" pageLibs={['apexcharts', 'jsvectormap']} navbarCondensed navbarFloating>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Navbar floating" pretitle="Overview" pageMenu="layout.navbar-floating" pageLibs={['apexcharts', 'vector-map']} navbarCondensed navbarFloating>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Navbar sticky" pretitle="Overview" pageMenu="layout.navbar-sticky" pageLibs={['apexcharts', 'jsvectormap']} navbarSticky>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Navbar sticky" pretitle="Overview" pageMenu="layout.navbar-sticky" pageLibs={['apexcharts', 'vector-map']} navbarSticky>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="RTL mode" pretitle="Overview" pageMenu="layout.rtl-mode" pageLibs={['apexcharts', 'jsvectormap']} rtl>
|
||||
<DefaultLayout title="RTL mode" pretitle="Overview" pageMenu="layout.rtl-mode" pageLibs={['apexcharts', 'vector-map']} rtl>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent rtl />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Vertical floating layout" pretitle="Overview" pageMenu="layout.vertical-floating" pageLibs={['apexcharts', 'jsvectormap']} navbarPosition="vertical" navbarFloating>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Vertical floating layout" pretitle="Overview" pageMenu="layout.vertical-floating" pageLibs={['apexcharts', 'vector-map']} navbarPosition="vertical" navbarFloating>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Right vertical layout" pretitle="Overview" pageMenu="layout.vertical-right" pageLibs={['apexcharts', 'jsvectormap']} navbarPosition="vertical" sidebarEnd>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Right vertical layout" pretitle="Overview" pageMenu="layout.vertical-right" pageLibs={['apexcharts', 'vector-map']} navbarPosition="vertical" sidebarEnd>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Vertical transparent layout" pretitle="Overview" pageMenu="layout.vertical-transparent" pageLibs={['apexcharts', 'jsvectormap']} navbarPosition="vertical" navbarTransparent>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Vertical transparent layout" pretitle="Overview" pageMenu="layout.vertical-transparent" pageLibs={['apexcharts', 'vector-map']} navbarPosition="vertical" navbarTransparent>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -4,7 +4,7 @@ import HomepageContent from '@shared/components/HomepageContent.astro'
|
||||
import HeaderActionsButtons from '@shared/components/layout/HeaderActionsButtons.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Dashboard" pageHeader="Vertical layout" pretitle="Overview" pageLibs={['apexcharts', 'jsvectormap']} pageMenu="layout.vertical" navbarPosition="vertical" sidebarFoldToggle>
|
||||
<DefaultLayout title="Dashboard" pageHeader="Vertical layout" pretitle="Overview" pageLibs={['apexcharts', 'vector-map']} pageMenu="layout.vertical" navbarPosition="vertical" sidebarFoldToggle>
|
||||
<HeaderActionsButtons slot="page-header-actions" />
|
||||
<HomepageContent />
|
||||
</DefaultLayout>
|
||||
@@ -7,14 +7,14 @@ import CardTitle from '@ui/CardTitle.astro'
|
||||
import DocsLink from '@ui/DocsLink.astro'
|
||||
---
|
||||
|
||||
<DefaultLayout title="Vector Maps" pageMenu="plugins.maps-vector" pageLibs={['jsvectormap']}>
|
||||
<DefaultLayout title="Vector Maps" pageMenu="plugins.maps-vector" pageLibs={['vector-map']}>
|
||||
<DocsLink slot="page-header-actions" path="/ui/plugins/vector-map" />
|
||||
<div class="row row-cards">
|
||||
<div class="col-md-6">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>World map</CardTitle>
|
||||
<MapVector mapId="world-merc" />
|
||||
<MapVector mapId="world" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -34,5 +34,13 @@ import DocsLink from '@ui/DocsLink.astro'
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Map with zoom</CardTitle>
|
||||
<MapVector mapId="world-zoom" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
@@ -73,10 +73,12 @@ const themeAttributes = themeDataAttributes({ ...theme, 'navbar-position': navba
|
||||
// whatever core's own watcher last emitted, which is unminified.
|
||||
const min = import.meta.env.DEV ? '' : '.min'
|
||||
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[] }
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[]; addon?: string; maps?: string[] }
|
||||
const pageLibEntries = Object.entries(libs as Record<string, Lib>).filter(([name]) => pageLibs.includes(name))
|
||||
const libHref = (lib: Lib, file: string) => `/dist/libs/${lib.npm}/${file}`
|
||||
const libCssFiles = pageLibEntries.flatMap(([, lib]) => (lib.css ?? []).map((file) => libHref(lib, file)))
|
||||
const libAddons = pageLibEntries.flatMap(([, lib]) => (lib.addon ? [lib.addon] : []))
|
||||
const libMaps = pageLibEntries.flatMap(([, lib]) => lib.maps ?? [])
|
||||
const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)))
|
||||
---
|
||||
|
||||
@@ -178,6 +180,8 @@ const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file)
|
||||
|
||||
{libJsFiles.map((src) => <script is:inline src={src} defer />)}
|
||||
<script is:inline src={`/dist/js/tabler${min}.js`} defer></script>
|
||||
{libAddons.map((addon) => <script is:inline src={`/dist/js/${addon}${min}.js`} defer />)}
|
||||
{libMaps.map((map) => <script is:inline src={`/dist/js/maps/${map}.js`} defer />)}
|
||||
|
||||
<PageScripts />
|
||||
|
||||
|
||||
@@ -45,10 +45,12 @@ const rtlSuffix = rtl ? '.rtl' : ''
|
||||
// build instead of hardcoding one variant.
|
||||
const min = import.meta.env.DEV ? '' : '.min'
|
||||
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[] }
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[]; addon?: string; maps?: string[] }
|
||||
const pageLibEntries = Object.entries(libs as Record<string, Lib>).filter(([name]) => pageLibs.includes(name))
|
||||
const libHref = (lib: Lib, file: string) => `/dist/libs/${lib.npm}/${file}`
|
||||
const libCssFiles = pageLibEntries.flatMap(([, lib]) => (lib.css ?? []).map((file) => libHref(lib, file)))
|
||||
const libAddons = pageLibEntries.flatMap(([, lib]) => (lib.addon ? [lib.addon] : []))
|
||||
const libMaps = pageLibEntries.flatMap(([, lib]) => lib.maps ?? [])
|
||||
const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)))
|
||||
---
|
||||
|
||||
@@ -169,6 +171,8 @@ const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file)
|
||||
|
||||
{libJsFiles.map((src) => <script is:inline src={src} defer />)}
|
||||
<script is:inline src={`/dist/js/tabler${min}.js`} defer></script>
|
||||
{libAddons.map((addon) => <script is:inline src={`/dist/js/${addon}${min}.js`} defer />)}
|
||||
{libMaps.map((map) => <script is:inline src={`/dist/js/maps/${map}.js`} defer />)}
|
||||
|
||||
<!-- components that render via CaptureScript (e.g. Chart) queue their init script
|
||||
into shared/lib/page-scripts instead of inlining it where used; this drains it. -->
|
||||
|
||||
@@ -7,14 +7,14 @@ import Subheader from '@ui/Subheader.astro'
|
||||
import ScaleLegend from '@components/demo/ScaleLegend.astro'
|
||||
---
|
||||
|
||||
<ScreenshotLayout title="Map" columns={2} pageLibs={['jsvectormap']} cssPlugins={['flags']}>
|
||||
<ScreenshotLayout title="Map" columns={2} pageLibs={['vector-map']} cssPlugins={['flags']}>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<h3 class="card-title m-0">Locations</h3>
|
||||
<ScaleLegend class="ms-auto" />
|
||||
</div>
|
||||
<MapVector mapId="world" color="primary" ratio="21x9" />
|
||||
<MapVector mapId="world" />
|
||||
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-4">
|
||||
|
||||
@@ -13,6 +13,6 @@ const map = 'world'
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{title && <CardTitle>{title}</CardTitle>}
|
||||
<MapVector mapId={map} color="primary" ratio="21x9" />
|
||||
<MapVector mapId={map} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,160 +1,39 @@
|
||||
---
|
||||
import mapsVector from '@data/maps-vector.json';
|
||||
import CaptureScript from '../CaptureScript.astro';
|
||||
import mapsVector from '@data/maps-vector.json'
|
||||
|
||||
interface MapData {
|
||||
title?: string;
|
||||
map: string;
|
||||
color?: string;
|
||||
filled?: boolean;
|
||||
zoom?: boolean;
|
||||
values?: Record<string, string>;
|
||||
markers?: { name: string; coords: string }[];
|
||||
lines?: { from: string; to: string }[];
|
||||
title?: string
|
||||
map: string
|
||||
color?: string
|
||||
zoom?: boolean
|
||||
values?: Record<string, number>
|
||||
markers?: { name: string; lat: number; lng: number }[]
|
||||
lines?: { from: string; to: string }[]
|
||||
}
|
||||
|
||||
interface Props {
|
||||
mapId: string;
|
||||
color?: string;
|
||||
ratio?: string;
|
||||
mapId: string
|
||||
color?: string
|
||||
}
|
||||
|
||||
const { mapId, color: colorProp, ratio = '4x3' } = Astro.props;
|
||||
const { mapId, color: colorProp } = Astro.props
|
||||
|
||||
const data = (mapsVector as Record<string, MapData>)[mapId];
|
||||
const color = colorProp ?? data?.color ?? 'primary';
|
||||
|
||||
// scale1..scale10 — a 10-step color-mix ramp against var(--tblr-primary).
|
||||
const scale = Object.fromEntries(
|
||||
Array.from({ length: 10 }, (_, n) => [
|
||||
`scale${n + 1}`,
|
||||
`color-mix(in srgb, transparent, var(--tblr-primary) ${(n + 1) * 10}%)`,
|
||||
]),
|
||||
);
|
||||
|
||||
const markers = data?.markers?.map((marker) => ({
|
||||
coords: marker.coords.split(',').map((n) => Number(n.trim())),
|
||||
name: marker.name,
|
||||
}));
|
||||
|
||||
const mapKey = `map-${mapId}`;
|
||||
const mapSelector = `#map-${mapId}`;
|
||||
const mapLabel = data?.title ?? 'Map';
|
||||
|
||||
// jsVectorMap renders its own zoom +/- buttons inside this container when
|
||||
// zoom is enabled. `role="img"` flattens all descendants for assistive
|
||||
// tech, which would swallow those buttons — so only apply it when there's
|
||||
// no interactive chrome to hide.
|
||||
const hasZoomControls = Boolean(data?.zoom);
|
||||
|
||||
const mapOptions = data
|
||||
? {
|
||||
backgroundColor: 'transparent',
|
||||
regionStyle: {
|
||||
initial: {
|
||||
fill: 'var(--tblr-bg-surface-secondary)',
|
||||
stroke: data.filled ? '#fff' : 'var(--tblr-border-color)',
|
||||
strokeWidth: data.filled ? 1 : 2,
|
||||
},
|
||||
},
|
||||
zoomOnScroll: Boolean(data.zoom),
|
||||
zoomButtons: Boolean(data.zoom),
|
||||
...(data.values
|
||||
? { series: { regions: [{ attribute: 'fill', scale, values: data.values }] } }
|
||||
: {}),
|
||||
...(markers
|
||||
? {
|
||||
markers,
|
||||
markerStyle: {
|
||||
initial: {
|
||||
r: 4,
|
||||
stroke: '#fff',
|
||||
opacity: 1,
|
||||
strokeWidth: 3,
|
||||
strokeOpacity: 0.5,
|
||||
fill: `var(--tblr-${color})`,
|
||||
},
|
||||
hover: { fill: `var(--tblr-${color})`, stroke: `var(--tblr-${color})` },
|
||||
},
|
||||
markerLabelStyle: { initial: { fontSize: 10 } },
|
||||
}
|
||||
: {}),
|
||||
...(data.lines
|
||||
? {
|
||||
lines: data.lines,
|
||||
lineStyle: {
|
||||
strokeDasharray: '4 4',
|
||||
animation: true,
|
||||
stroke: 'rgba(98, 105, 118, .75)',
|
||||
strokeWidth: 0.5,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
: undefined;
|
||||
const data = (mapsVector as Record<string, MapData>)[mapId]
|
||||
const color = colorProp ?? data?.color ?? 'primary'
|
||||
---
|
||||
|
||||
{
|
||||
data && (
|
||||
<Fragment>
|
||||
<div class={`ratio ratio-${ratio}`}>
|
||||
<div>
|
||||
<div
|
||||
id={mapKey}
|
||||
class="w-100 h-100"
|
||||
role={hasZoomControls ? undefined : 'img'}
|
||||
aria-label={mapLabel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CaptureScript>
|
||||
<!-- BEGIN MAP VECTOR -->
|
||||
<script is:inline define:vars={{ mapKey, mapSelector, mapName: data.map, mapOptions }}>
|
||||
window.tabler_map_vector ??= {};
|
||||
|
||||
function createMapVector() {
|
||||
const map = (window.tabler_map_vector[mapKey] = new jsVectorMap({
|
||||
selector: mapSelector,
|
||||
map: mapName,
|
||||
...mapOptions,
|
||||
// labels.markers.render is a function, not JSON-serializable — added here
|
||||
// rather than baked into mapOptions.
|
||||
...(mapOptions.markers ? { labels: { markers: { render: (marker) => marker.name } } } : {}),
|
||||
}));
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
map.updateSize();
|
||||
});
|
||||
}
|
||||
|
||||
function initMapVector() {
|
||||
const container = document.querySelector(mapSelector);
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// jsVectorMap computes its initial scale from the container's layout
|
||||
// size. If the container is still 0x0 (e.g. layout hasn't settled
|
||||
// yet), that scale ends up 0, and a later resize divides by it,
|
||||
// producing a NaN transform. Wait for real dimensions first.
|
||||
if (container.offsetWidth && container.offsetHeight) {
|
||||
createMapVector();
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
if (entries[0].contentRect.width && entries[0].contentRect.height) {
|
||||
observer.disconnect();
|
||||
createMapVector();
|
||||
}
|
||||
});
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
document.readyState !== 'loading' ? initMapVector() : document.addEventListener('DOMContentLoaded', initMapVector, { once: true });
|
||||
</script>
|
||||
<!-- END MAP VECTOR -->
|
||||
</CaptureScript>
|
||||
</Fragment>
|
||||
)
|
||||
data && (
|
||||
<div
|
||||
class="vector-map"
|
||||
data-bs-toggle="vector-map"
|
||||
data-bs-map={data.map}
|
||||
data-bs-label={data.title}
|
||||
data-bs-zoom={data.zoom ? 'true' : undefined}
|
||||
data-bs-values={data.values && JSON.stringify(data.values)}
|
||||
data-bs-markers={data.markers && JSON.stringify(data.markers)}
|
||||
data-bs-lines={data.lines && JSON.stringify(data.lines)}
|
||||
style={color === 'primary' ? undefined : `--tblr-vector-map-color: var(--tblr-${color})`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+220
-393
@@ -9,425 +9,225 @@
|
||||
"map": "world",
|
||||
"color": "primary",
|
||||
"values": {
|
||||
"AF": "scale2",
|
||||
"AL": "scale2",
|
||||
"DZ": "scale4",
|
||||
"AO": "scale3",
|
||||
"AG": "scale1",
|
||||
"AR": "scale5",
|
||||
"AM": "scale1",
|
||||
"AU": "scale7",
|
||||
"AT": "scale5",
|
||||
"AZ": "scale3",
|
||||
"BS": "scale1",
|
||||
"BH": "scale2",
|
||||
"BD": "scale4",
|
||||
"BB": "scale1",
|
||||
"BY": "scale3",
|
||||
"BE": "scale5",
|
||||
"BZ": "scale1",
|
||||
"BJ": "scale1",
|
||||
"BT": "scale1",
|
||||
"BO": "scale2",
|
||||
"BA": "scale2",
|
||||
"BW": "scale2",
|
||||
"BR": "scale8",
|
||||
"BN": "scale2",
|
||||
"BG": "scale2",
|
||||
"BF": "scale1",
|
||||
"BI": "scale1",
|
||||
"KH": "scale2",
|
||||
"CM": "scale2",
|
||||
"CA": "scale7",
|
||||
"CV": "scale1",
|
||||
"CF": "scale1",
|
||||
"TD": "scale1",
|
||||
"CL": "scale4",
|
||||
"CN": "scale9",
|
||||
"CO": "scale5",
|
||||
"KM": "scale1",
|
||||
"CD": "scale2",
|
||||
"CG": "scale2",
|
||||
"CR": "scale2",
|
||||
"CI": "scale2",
|
||||
"HR": "scale3",
|
||||
"CY": "scale2",
|
||||
"CZ": "scale4",
|
||||
"DK": "scale5",
|
||||
"DJ": "scale1",
|
||||
"DM": "scale1",
|
||||
"DO": "scale3",
|
||||
"EC": "scale3",
|
||||
"EG": "scale5",
|
||||
"SV": "scale2",
|
||||
"GQ": "scale2",
|
||||
"ER": "scale1",
|
||||
"EE": "scale2",
|
||||
"ET": "scale2",
|
||||
"FJ": "scale1",
|
||||
"FI": "scale5",
|
||||
"FR": "scale8",
|
||||
"GA": "scale2",
|
||||
"GM": "scale1",
|
||||
"GE": "scale2",
|
||||
"DE": "scale8",
|
||||
"GH": "scale2",
|
||||
"GR": "scale5",
|
||||
"GD": "scale1",
|
||||
"GT": "scale2",
|
||||
"GN": "scale1",
|
||||
"GW": "scale1",
|
||||
"GY": "scale1",
|
||||
"HT": "scale1",
|
||||
"HN": "scale2",
|
||||
"HK": "scale5",
|
||||
"HU": "scale4",
|
||||
"IS": "scale2",
|
||||
"IN": "scale7",
|
||||
"ID": "scale6",
|
||||
"IR": "scale5",
|
||||
"IQ": "scale3",
|
||||
"IE": "scale5",
|
||||
"IL": "scale5",
|
||||
"IT": "scale8",
|
||||
"JM": "scale2",
|
||||
"JP": "scale9",
|
||||
"JO": "scale2",
|
||||
"KZ": "scale4",
|
||||
"KE": "scale2",
|
||||
"KI": "scale1",
|
||||
"KR": "scale6",
|
||||
"KW": "scale4",
|
||||
"KG": "scale1",
|
||||
"LA": "scale1",
|
||||
"LV": "scale2",
|
||||
"LB": "scale2",
|
||||
"LS": "scale1",
|
||||
"LR": "scale1",
|
||||
"LY": "scale3",
|
||||
"LT": "scale2",
|
||||
"LU": "scale3",
|
||||
"MK": "scale1",
|
||||
"MG": "scale1",
|
||||
"MW": "scale1",
|
||||
"MY": "scale5",
|
||||
"MV": "scale1",
|
||||
"ML": "scale1",
|
||||
"MT": "scale1",
|
||||
"MR": "scale1",
|
||||
"MU": "scale1",
|
||||
"MX": "scale7",
|
||||
"MD": "scale1",
|
||||
"MN": "scale1",
|
||||
"ME": "scale1",
|
||||
"MA": "scale3",
|
||||
"MZ": "scale2",
|
||||
"MM": "scale2",
|
||||
"NA": "scale2",
|
||||
"NP": "scale2",
|
||||
"NL": "scale6",
|
||||
"NZ": "scale4",
|
||||
"NI": "scale1",
|
||||
"NE": "scale1",
|
||||
"NG": "scale5",
|
||||
"NO": "scale5",
|
||||
"OM": "scale3",
|
||||
"PK": "scale4",
|
||||
"PA": "scale2",
|
||||
"PG": "scale1",
|
||||
"PY": "scale2",
|
||||
"PE": "scale4",
|
||||
"PH": "scale4",
|
||||
"PL": "scale10",
|
||||
"PT": "scale5",
|
||||
"QA": "scale4",
|
||||
"RO": "scale4",
|
||||
"RU": "scale7",
|
||||
"RW": "scale1",
|
||||
"WS": "scale1",
|
||||
"ST": "scale1",
|
||||
"SA": "scale5",
|
||||
"SN": "scale2",
|
||||
"RS": "scale2",
|
||||
"SC": "scale1",
|
||||
"SL": "scale1",
|
||||
"SG": "scale5",
|
||||
"SK": "scale3",
|
||||
"SI": "scale2",
|
||||
"SB": "scale1",
|
||||
"ZA": "scale5",
|
||||
"ES": "scale7",
|
||||
"LK": "scale2",
|
||||
"KN": "scale1",
|
||||
"LC": "scale1",
|
||||
"VC": "scale1",
|
||||
"SD": "scale3",
|
||||
"SR": "scale1",
|
||||
"SZ": "scale1",
|
||||
"SE": "scale5",
|
||||
"CH": "scale6",
|
||||
"SY": "scale3",
|
||||
"TW": "scale5",
|
||||
"TJ": "scale1",
|
||||
"TZ": "scale2",
|
||||
"TH": "scale5",
|
||||
"TL": "scale1",
|
||||
"TG": "scale1",
|
||||
"TO": "scale1",
|
||||
"TT": "scale2",
|
||||
"TN": "scale2",
|
||||
"TR": "scale6",
|
||||
"TM": "scale1",
|
||||
"UG": "scale2",
|
||||
"UA": "scale4",
|
||||
"AE": "scale5",
|
||||
"GB": "scale8",
|
||||
"US": "scale10",
|
||||
"UY": "scale2",
|
||||
"UZ": "scale2",
|
||||
"VU": "scale1",
|
||||
"VE": "scale5",
|
||||
"VN": "scale4",
|
||||
"YE": "scale2",
|
||||
"ZM": "scale2",
|
||||
"ZW": "scale1"
|
||||
}
|
||||
},
|
||||
"world-merc": {
|
||||
"title": "World map",
|
||||
"map": "world_merc",
|
||||
"color": "primary",
|
||||
"values": {
|
||||
"AF": "scale2",
|
||||
"AL": "scale2",
|
||||
"DZ": "scale4",
|
||||
"AO": "scale3",
|
||||
"AG": "scale1",
|
||||
"AR": "scale5",
|
||||
"AM": "scale1",
|
||||
"AU": "scale7",
|
||||
"AT": "scale5",
|
||||
"AZ": "scale3",
|
||||
"BS": "scale1",
|
||||
"BH": "scale2",
|
||||
"BD": "scale4",
|
||||
"BB": "scale1",
|
||||
"BY": "scale3",
|
||||
"BE": "scale5",
|
||||
"BZ": "scale1",
|
||||
"BJ": "scale1",
|
||||
"BT": "scale1",
|
||||
"BO": "scale2",
|
||||
"BA": "scale2",
|
||||
"BW": "scale2",
|
||||
"BR": "scale8",
|
||||
"BN": "scale2",
|
||||
"BG": "scale2",
|
||||
"BF": "scale1",
|
||||
"BI": "scale1",
|
||||
"KH": "scale2",
|
||||
"CM": "scale2",
|
||||
"CA": "scale7",
|
||||
"CV": "scale1",
|
||||
"CF": "scale1",
|
||||
"TD": "scale1",
|
||||
"CL": "scale4",
|
||||
"CN": "scale9",
|
||||
"CO": "scale5",
|
||||
"KM": "scale1",
|
||||
"CD": "scale2",
|
||||
"CG": "scale2",
|
||||
"CR": "scale2",
|
||||
"CI": "scale2",
|
||||
"HR": "scale3",
|
||||
"CY": "scale2",
|
||||
"CZ": "scale4",
|
||||
"DK": "scale5",
|
||||
"DJ": "scale1",
|
||||
"DM": "scale1",
|
||||
"DO": "scale3",
|
||||
"EC": "scale3",
|
||||
"EG": "scale5",
|
||||
"SV": "scale2",
|
||||
"GQ": "scale2",
|
||||
"ER": "scale1",
|
||||
"EE": "scale2",
|
||||
"ET": "scale2",
|
||||
"FJ": "scale1",
|
||||
"FI": "scale5",
|
||||
"FR": "scale8",
|
||||
"GA": "scale2",
|
||||
"GM": "scale1",
|
||||
"GE": "scale2",
|
||||
"DE": "scale8",
|
||||
"GH": "scale2",
|
||||
"GR": "scale5",
|
||||
"GD": "scale1",
|
||||
"GT": "scale2",
|
||||
"GN": "scale1",
|
||||
"GW": "scale1",
|
||||
"GY": "scale1",
|
||||
"HT": "scale1",
|
||||
"HN": "scale2",
|
||||
"HK": "scale5",
|
||||
"HU": "scale4",
|
||||
"IS": "scale2",
|
||||
"IN": "scale7",
|
||||
"ID": "scale6",
|
||||
"IR": "scale5",
|
||||
"IQ": "scale3",
|
||||
"IE": "scale5",
|
||||
"IL": "scale5",
|
||||
"IT": "scale8",
|
||||
"JM": "scale2",
|
||||
"JP": "scale9",
|
||||
"JO": "scale2",
|
||||
"KZ": "scale4",
|
||||
"KE": "scale2",
|
||||
"KI": "scale1",
|
||||
"KR": "scale6",
|
||||
"KW": "scale4",
|
||||
"KG": "scale1",
|
||||
"LA": "scale1",
|
||||
"LV": "scale2",
|
||||
"LB": "scale2",
|
||||
"LS": "scale1",
|
||||
"LR": "scale1",
|
||||
"LY": "scale3",
|
||||
"LT": "scale2",
|
||||
"LU": "scale3",
|
||||
"MK": "scale1",
|
||||
"MG": "scale1",
|
||||
"MW": "scale1",
|
||||
"MY": "scale5",
|
||||
"MV": "scale1",
|
||||
"ML": "scale1",
|
||||
"MT": "scale1",
|
||||
"MR": "scale1",
|
||||
"MU": "scale1",
|
||||
"MX": "scale7",
|
||||
"MD": "scale1",
|
||||
"MN": "scale1",
|
||||
"ME": "scale1",
|
||||
"MA": "scale3",
|
||||
"MZ": "scale2",
|
||||
"MM": "scale2",
|
||||
"NA": "scale2",
|
||||
"NP": "scale2",
|
||||
"NL": "scale6",
|
||||
"NZ": "scale4",
|
||||
"NI": "scale1",
|
||||
"NE": "scale1",
|
||||
"NG": "scale5",
|
||||
"NO": "scale5",
|
||||
"OM": "scale3",
|
||||
"PK": "scale4",
|
||||
"PA": "scale2",
|
||||
"PG": "scale1",
|
||||
"PY": "scale2",
|
||||
"PE": "scale4",
|
||||
"PH": "scale4",
|
||||
"PL": "scale10",
|
||||
"PT": "scale5",
|
||||
"QA": "scale4",
|
||||
"RO": "scale4",
|
||||
"RU": "scale7",
|
||||
"RW": "scale1",
|
||||
"WS": "scale1",
|
||||
"ST": "scale1",
|
||||
"SA": "scale5",
|
||||
"SN": "scale2",
|
||||
"RS": "scale2",
|
||||
"SC": "scale1",
|
||||
"SL": "scale1",
|
||||
"SG": "scale5",
|
||||
"SK": "scale3",
|
||||
"SI": "scale2",
|
||||
"SB": "scale1",
|
||||
"ZA": "scale5",
|
||||
"ES": "scale7",
|
||||
"LK": "scale2",
|
||||
"KN": "scale1",
|
||||
"LC": "scale1",
|
||||
"VC": "scale1",
|
||||
"SD": "scale3",
|
||||
"SR": "scale1",
|
||||
"SZ": "scale1",
|
||||
"SE": "scale5",
|
||||
"CH": "scale6",
|
||||
"SY": "scale3",
|
||||
"TW": "scale5",
|
||||
"TJ": "scale1",
|
||||
"TZ": "scale2",
|
||||
"TH": "scale5",
|
||||
"TL": "scale1",
|
||||
"TG": "scale1",
|
||||
"TO": "scale1",
|
||||
"TT": "scale2",
|
||||
"TN": "scale2",
|
||||
"TR": "scale6",
|
||||
"TM": "scale1",
|
||||
"UG": "scale2",
|
||||
"UA": "scale4",
|
||||
"AE": "scale5",
|
||||
"GB": "scale8",
|
||||
"US": "scale10",
|
||||
"UY": "scale2",
|
||||
"UZ": "scale2",
|
||||
"VU": "scale1",
|
||||
"VE": "scale5",
|
||||
"VN": "scale4",
|
||||
"YE": "scale2",
|
||||
"ZM": "scale2",
|
||||
"ZW": "scale1"
|
||||
"AF": 2,
|
||||
"AL": 2,
|
||||
"DZ": 4,
|
||||
"AO": 3,
|
||||
"AR": 5,
|
||||
"AM": 1,
|
||||
"AU": 7,
|
||||
"AT": 5,
|
||||
"AZ": 3,
|
||||
"BS": 1,
|
||||
"BD": 4,
|
||||
"BY": 3,
|
||||
"BE": 5,
|
||||
"BZ": 1,
|
||||
"BJ": 1,
|
||||
"BT": 1,
|
||||
"BO": 2,
|
||||
"BA": 2,
|
||||
"BW": 2,
|
||||
"BR": 8,
|
||||
"BN": 2,
|
||||
"BG": 2,
|
||||
"BF": 1,
|
||||
"BI": 1,
|
||||
"KH": 2,
|
||||
"CM": 2,
|
||||
"CA": 7,
|
||||
"CF": 1,
|
||||
"TD": 1,
|
||||
"CL": 4,
|
||||
"CN": 9,
|
||||
"CO": 5,
|
||||
"CD": 2,
|
||||
"CG": 2,
|
||||
"CR": 2,
|
||||
"CI": 2,
|
||||
"HR": 3,
|
||||
"CY": 2,
|
||||
"CZ": 4,
|
||||
"DK": 5,
|
||||
"DJ": 1,
|
||||
"DO": 3,
|
||||
"EC": 3,
|
||||
"EG": 5,
|
||||
"SV": 2,
|
||||
"GQ": 2,
|
||||
"ER": 1,
|
||||
"EE": 2,
|
||||
"ET": 2,
|
||||
"FJ": 1,
|
||||
"FI": 5,
|
||||
"FR": 8,
|
||||
"GA": 2,
|
||||
"GM": 1,
|
||||
"GE": 2,
|
||||
"DE": 8,
|
||||
"GH": 2,
|
||||
"GR": 5,
|
||||
"GT": 2,
|
||||
"GN": 1,
|
||||
"GW": 1,
|
||||
"GY": 1,
|
||||
"HT": 1,
|
||||
"HN": 2,
|
||||
"HU": 4,
|
||||
"IS": 2,
|
||||
"IN": 7,
|
||||
"ID": 6,
|
||||
"IR": 5,
|
||||
"IQ": 3,
|
||||
"IE": 5,
|
||||
"IL": 5,
|
||||
"IT": 8,
|
||||
"JM": 2,
|
||||
"JP": 9,
|
||||
"JO": 2,
|
||||
"KZ": 4,
|
||||
"KE": 2,
|
||||
"KR": 6,
|
||||
"KW": 4,
|
||||
"KG": 1,
|
||||
"LA": 1,
|
||||
"LV": 2,
|
||||
"LB": 2,
|
||||
"LS": 1,
|
||||
"LR": 1,
|
||||
"LY": 3,
|
||||
"LT": 2,
|
||||
"LU": 3,
|
||||
"MK": 1,
|
||||
"MG": 1,
|
||||
"MW": 1,
|
||||
"MY": 5,
|
||||
"ML": 1,
|
||||
"MR": 1,
|
||||
"MX": 7,
|
||||
"MD": 1,
|
||||
"MN": 1,
|
||||
"ME": 1,
|
||||
"MA": 3,
|
||||
"MZ": 2,
|
||||
"MM": 2,
|
||||
"NA": 2,
|
||||
"NP": 2,
|
||||
"NL": 6,
|
||||
"NZ": 4,
|
||||
"NI": 1,
|
||||
"NE": 1,
|
||||
"NG": 5,
|
||||
"NO": 5,
|
||||
"OM": 3,
|
||||
"PK": 4,
|
||||
"PA": 2,
|
||||
"PG": 1,
|
||||
"PY": 2,
|
||||
"PE": 4,
|
||||
"PH": 4,
|
||||
"PL": 10,
|
||||
"PT": 5,
|
||||
"QA": 4,
|
||||
"RO": 4,
|
||||
"RU": 7,
|
||||
"RW": 1,
|
||||
"SA": 5,
|
||||
"SN": 2,
|
||||
"RS": 2,
|
||||
"SL": 1,
|
||||
"SK": 3,
|
||||
"SI": 2,
|
||||
"SB": 1,
|
||||
"ZA": 5,
|
||||
"ES": 7,
|
||||
"LK": 2,
|
||||
"SD": 3,
|
||||
"SR": 1,
|
||||
"SZ": 1,
|
||||
"SE": 5,
|
||||
"CH": 6,
|
||||
"SY": 3,
|
||||
"TW": 5,
|
||||
"TJ": 1,
|
||||
"TZ": 2,
|
||||
"TH": 5,
|
||||
"TL": 1,
|
||||
"TG": 1,
|
||||
"TT": 2,
|
||||
"TN": 2,
|
||||
"TR": 6,
|
||||
"TM": 1,
|
||||
"UG": 2,
|
||||
"UA": 4,
|
||||
"AE": 5,
|
||||
"GB": 8,
|
||||
"US": 10,
|
||||
"UY": 2,
|
||||
"UZ": 2,
|
||||
"VU": 1,
|
||||
"VE": 5,
|
||||
"VN": 4,
|
||||
"YE": 2,
|
||||
"ZM": 2,
|
||||
"ZW": 1
|
||||
}
|
||||
},
|
||||
"world-markers": {
|
||||
"title": "Map with markers",
|
||||
"map": "world_merc",
|
||||
"map": "world",
|
||||
"color": "red",
|
||||
"markers": [
|
||||
{
|
||||
"name": "Russia",
|
||||
"coords": "61.524, 105.3188"
|
||||
"lat": 61.524,
|
||||
"lng": 105.3188
|
||||
},
|
||||
{
|
||||
"name": "Canada",
|
||||
"coords": "56.1304, -106.3468"
|
||||
"lat": 56.1304,
|
||||
"lng": -106.3468
|
||||
},
|
||||
{
|
||||
"name": "Greenland",
|
||||
"coords": "71.7069, -42.6043"
|
||||
"lat": 71.7069,
|
||||
"lng": -42.6043
|
||||
},
|
||||
{
|
||||
"name": "Egypt",
|
||||
"coords": "26.8206, 30.8025"
|
||||
"lat": 26.8206,
|
||||
"lng": 30.8025
|
||||
},
|
||||
{
|
||||
"name": "Brazil",
|
||||
"coords": "-14.235, -51.9253"
|
||||
"lat": -14.235,
|
||||
"lng": -51.9253
|
||||
},
|
||||
{
|
||||
"name": "China",
|
||||
"coords": "35.8617, 104.1954"
|
||||
"lat": 35.8617,
|
||||
"lng": 104.1954
|
||||
},
|
||||
{
|
||||
"name": "United States",
|
||||
"coords": "37.0902, -95.7129"
|
||||
"lat": 37.0902,
|
||||
"lng": -95.7129
|
||||
},
|
||||
{
|
||||
"name": "Norway",
|
||||
"coords": "60.472024, 8.468946"
|
||||
"lat": 60.472024,
|
||||
"lng": 8.468946
|
||||
},
|
||||
{
|
||||
"name": "Ukraine",
|
||||
"coords": "48.379433, 31.16558"
|
||||
"lat": 48.379433,
|
||||
"lng": 31.16558
|
||||
}
|
||||
]
|
||||
},
|
||||
"world-lines": {
|
||||
"title": "Map with markers",
|
||||
"map": "world_merc",
|
||||
"title": "Map with markers and lines",
|
||||
"map": "world",
|
||||
"color": "primary",
|
||||
"lines": [
|
||||
{
|
||||
@@ -462,40 +262,67 @@
|
||||
"markers": [
|
||||
{
|
||||
"name": "Russia",
|
||||
"coords": "61.524, 105.3188"
|
||||
"lat": 61.524,
|
||||
"lng": 105.3188
|
||||
},
|
||||
{
|
||||
"name": "Canada",
|
||||
"coords": "56.1304, -106.3468"
|
||||
"lat": 56.1304,
|
||||
"lng": -106.3468
|
||||
},
|
||||
{
|
||||
"name": "Greenland",
|
||||
"coords": "71.7069, -42.6043"
|
||||
"lat": 71.7069,
|
||||
"lng": -42.6043
|
||||
},
|
||||
{
|
||||
"name": "Egypt",
|
||||
"coords": "26.8206, 30.8025"
|
||||
"lat": 26.8206,
|
||||
"lng": 30.8025
|
||||
},
|
||||
{
|
||||
"name": "Brazil",
|
||||
"coords": "-14.235, -51.9253"
|
||||
"lat": -14.235,
|
||||
"lng": -51.9253
|
||||
},
|
||||
{
|
||||
"name": "China",
|
||||
"coords": "35.8617, 104.1954"
|
||||
"lat": 35.8617,
|
||||
"lng": 104.1954
|
||||
},
|
||||
{
|
||||
"name": "United States",
|
||||
"coords": "37.0902, -95.7129"
|
||||
"lat": 37.0902,
|
||||
"lng": -95.7129
|
||||
},
|
||||
{
|
||||
"name": "Norway",
|
||||
"coords": "60.472024, 8.468946"
|
||||
"lat": 60.472024,
|
||||
"lng": 8.468946
|
||||
},
|
||||
{
|
||||
"name": "Ukraine",
|
||||
"coords": "48.379433, 31.16558"
|
||||
"lat": 48.379433,
|
||||
"lng": 31.16558
|
||||
}
|
||||
]
|
||||
},
|
||||
"world-zoom": {
|
||||
"title": "Map with zoom",
|
||||
"map": "world",
|
||||
"color": "green",
|
||||
"zoom": true,
|
||||
"values": {
|
||||
"PL": 8,
|
||||
"DE": 6,
|
||||
"FR": 5,
|
||||
"ES": 3,
|
||||
"IT": 4,
|
||||
"GB": 7,
|
||||
"SE": 2,
|
||||
"UA": 3,
|
||||
"RO": 2,
|
||||
"CZ": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,13 @@
|
||||
"description": "Lightweight JavaScript library for creating interactive maps and data visualizations.",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "Natural Earth",
|
||||
"lib": "vector-map",
|
||||
"url": "https://www.naturalearthdata.com/",
|
||||
"description": "Public domain map dataset, the source of the country shapes in the vector map.",
|
||||
"license": "Public Domain"
|
||||
},
|
||||
{
|
||||
"name": "List.js",
|
||||
"npm": "list.js",
|
||||
|
||||
@@ -9,7 +9,7 @@ import { themeDefaults, themePresets, sidebarOnlyKeys, navbarOnlyKeys, themeData
|
||||
|
||||
interface Props {
|
||||
title?: string | undefined
|
||||
/** Third-party libs from libs.json, e.g. ['apexcharts', 'jsvectormap'] */
|
||||
/** Third-party libs from libs.json, e.g. ['apexcharts', 'vector-map'] */
|
||||
pageLibs?: string[] | undefined
|
||||
/** Extra body classes, e.g. "layout-boxed" / "layout-fluid" */
|
||||
bodyClass?: string | undefined
|
||||
@@ -39,7 +39,7 @@ const isProduction = process.env.VERCEL_ENV === 'production'
|
||||
// RTL swaps tabler/plugin stylesheets for their .rtl.css variants (demo stays LTR)
|
||||
const rtlSuffix = rtl ? '.rtl' : ''
|
||||
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[]; head?: boolean }
|
||||
type Lib = { npm?: string; js?: string[]; css?: string[]; head?: boolean; addon?: string; maps?: string[] }
|
||||
const unknownLibs = pageLibs.filter((name) => !(name in libs))
|
||||
if (unknownLibs.length > 0) {
|
||||
throw new Error(`BaseLayout: unknown pageLibs ${unknownLibs.map((name) => `"${name}"`).join(', ')} — use a key from core/libs.json`)
|
||||
@@ -52,6 +52,10 @@ const googleMapsKey = import.meta.env.GOOGLE_MAPS_KEY ?? process.env.GOOGLE_MAPS
|
||||
// replaced); local files as ./dist/libs/{npm}/{file}.
|
||||
const libHref = (lib: Lib, file: string) => (/^https?:\/\//.test(file) ? file.replace(googleMapsKey ? 'GOOGLE_MAPS_KEY' : '?key=GOOGLE_MAPS_KEY', googleMapsKey) : `${base}/dist/libs/${lib.npm}/${file}`)
|
||||
const libCssFiles = pageLibEntries.flatMap(([, lib]) => (lib.css ?? []).map((file) => libHref(lib, file)))
|
||||
// Tabler add-on bundles (dist/js) a page needs, loaded after tabler.js, and the
|
||||
// map files of the vector map (dist/js/maps), which are plain data.
|
||||
const libAddons = pageLibEntries.flatMap(([, lib]) => (lib.addon ? [lib.addon] : []))
|
||||
const libMaps = pageLibEntries.flatMap(([, lib]) => lib.maps ?? [])
|
||||
const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean(lib.head) === head).flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)))
|
||||
---
|
||||
|
||||
@@ -136,6 +140,8 @@ const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean
|
||||
|
||||
<!-- BEGIN GLOBAL MANDATORY SCRIPTS -->
|
||||
<script is:inline src={`${base}/dist/js/tabler${min}.js`} defer></script>
|
||||
{libAddons.map((addon) => <script is:inline src={`${base}/dist/js/${addon}${min}.js`} defer />)}
|
||||
{libMaps.map((map) => <script is:inline src={`${base}/dist/js/maps/${map}.js`} defer />)}
|
||||
<!-- END GLOBAL MANDATORY SCRIPTS -->
|
||||
|
||||
<PageScripts />
|
||||
|
||||
Reference in new issue
Block a user