Skip to content

Commit b69b31a

Browse files
feat(lib): Implements Geographic data representation of Orange Unified Design System (#843)
* Work in progress * Productive day of work * Advancement on default definition * A solution THAT NEEDS echarts 6 !!!!! * Handle Bubble map * Remove opacity * fix france map * Add opacity, orange on buble * display corsica * Add alert about map * Last changes * Add Stackblitz workaround * [REVIEW] Suggestion to concentrate the added js code in one place, ie the stackblitz button * [REVIEW] Align legends --------- Co-authored-by: Louis-Maxime Piton <louismaxime.piton@orange.com>
1 parent cfebdbd commit b69b31a

File tree

10 files changed

+553
-44
lines changed

10 files changed

+553
-44
lines changed

docs/src/assets/stackblitz.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ document.querySelectorAll('.btn-edit').forEach((btn) => {
66
document.getElementById(`collapse_content_2_${id}`)?.classList.add('d-block');
77
document.getElementById(`${id}_viewCode`)?.classList.add('d-block');
88
const htmlText = document.getElementById(`${id}_html`).innerText; // Problème pour le formatage
9-
const codeText = document.getElementById(`${id}_code`).innerText;
9+
let codeText = btn.getAttribute('data-additional-js-before') ? btn.getAttribute('data-additional-js-before') + '\n\n' : '';
10+
codeText += document.getElementById(`${id}_code`).innerText;
11+
codeText += btn.getAttribute('data-additional-js-after') ? '\n\n' + btn.getAttribute('data-additional-js-after') : '';
1012
document.getElementById(`collapse_content_2_${id}`)?.classList.remove('d-block');
1113
document.getElementById(`${id}_viewCode`)?.classList.remove('d-block');
1214

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
title: Geographic Data - Examples - ODS Charts
3+
---
4+
5+
<div class="title-bar">
6+
<div class="container-xxl">
7+
<h1 class="display-1">Geographic data example</h1>
8+
</div>
9+
</div>
10+
<div class="container-xxl d-flex flex-nowrap pt-3">
11+
<div class="alert alert-warning">
12+
<span class="alert-icon"></span>
13+
<div>
14+
<h2 class="alert-heading mb-2"><span class="visually-hidden">Warning about </span>Map assets</h2>
15+
<p>The maps used in this page aren't studied for a commercial use. We use them as placeholders in our docs. We're trying our best to provide some, that you'll be able to use on your side. In the meantime, be sure to check the licenses associated to maps you use.</p>
16+
</div>
17+
</div>
18+
</div>
19+
<div class="container-xxl d-flex flex-nowrap pt-3">
20+
<div class="card w-100">
21+
<div class="card-body">
22+
<h5 class="card-title">Choropleth map</h5>
23+
<p class="card-text pe-5">A choropleth map uses colour progression to represent specific amounts of data. These are best used when comparing a dataset by a specific geographic region, which can show variation or patterns across the displayed location.</p>
24+
<button class="btn btn-icon btn-outline-secondary btn-edit" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Open in playground" data-additional-js-before="fetch('https://raw.githubusercontent.com/gregoiredavid/france-geojson/refs/heads/master/regions-version-simplifiee.geojson').then((res) => res.json()).then((json) => JSON.parse(JSON.stringify(json).replaceAll('nom', 'name'))).then((res) => echarts.registerMap('france-regions', res)).then(res => {"
25+
data-additional-js-after="});"
26+
>
27+
<svg width="1.25rem" height="1.25rem" fill="currentColor" aria-hidden="true">
28+
<use xlink:href="#lightning-charge-fill" />
29+
</svg>
30+
<span class="visually-hidden">Open in playground using StackBlitz</span>
31+
</button>
32+
<div id="choropletRegionshMapChart" style="min-height: 500px"></div>
33+
<script>
34+
window.addEventListener('DOMContentLoaded', () => {
35+
window.generateChoroplethMapChart('choropletRegionshMapChart');
36+
});
37+
</script>
38+
</div>
39+
</div>
40+
</div>
41+
<div class="container-xxl d-flex flex-nowrap pt-3">
42+
<div class="card w-100">
43+
<div class="card-body">
44+
<h5 class="card-title">Bubble map</h5>
45+
<p class="card-text pe-5">A bubble map uses circles over a specific geographical region. The area or size of the circle is proportional depending on the size or value of the dataset. These maps are best when comparing proportions over geographical regions that are close to one another.</p>
46+
<button class="btn btn-icon btn-outline-secondary btn-edit" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Open in playground" data-additional-js-before="fetch('https://raw.githubusercontent.com/apache/echarts-website/refs/heads/asf-site/examples/data/asset/geo/world.json').then((response) => response.json()).then((res) => echarts.registerMap('world', res)).then(res => {"
47+
data-additional-js-after="});">
48+
<svg width="1.25rem" height="1.25rem" fill="currentColor" aria-hidden="true">
49+
<use xlink:href="#lightning-charge-fill" />
50+
</svg>
51+
<span class="visually-hidden">Open in playground using StackBlitz</span>
52+
</button>
53+
<div id="bubbleMapChart" style="min-height: 500px"></div>
54+
<script>
55+
window.addEventListener('DOMContentLoaded', () => {
56+
window.generateBubbleMapChart('bubbleMapChart');
57+
});
58+
</script>
59+
</div>
60+
</div>
61+
</div>

docs/src/content/use_cases/specify-color.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ series: [
558558
<div class="card w-100">
559559
<div class="card-body">
560560
<h5 class="card-title">Bar specific color</h5>
561-
<p class="card-text pe-5">This example shows the example of using two colors for a single series. But this time, we actually keep a single series, displayed once in the legend, while modifying the color of the bars that are below the objective.</p>
561+
<p class="card-text pe-5">This example shows the example of using two colors for a single series. But this time, we actually keep a single series, displayed once in the legend, while modifying the color of the bars that are below the objective.</p>
562562
<p>To do this, we associate in the list of values the red color with the value below the objectives via the Apache ECharts <code>itemStyle.color</code> parameter:
563563
<code>
564564
<pre>
@@ -573,12 +573,12 @@ series: [
573573
itemStyle: {
574574
color: 'var(--ouds-charts-color-functional-negative)'
575575
}
576-
}
576+
}
577577
]
578578
}
579579
</pre>
580-
</code>
581-
</p>
580+
</code>
581+
</p>
582582
<button class="btn btn-icon btn-outline-secondary btn-edit" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Open in playground">
583583
<svg width="1.25rem" height="1.25rem" fill="currentColor" aria-hidden="true">
584584
<use xlink:href="#lightning-charge-fill" />
@@ -616,7 +616,7 @@ series: [
616616
}).map((res, i) => (res >= goals[i] ? res : {
617617
value: res,
618618
itemStyle: {
619-
color: 'var(--ouds-charts-color-functional-negative)',
619+
color: 'var(--ouds-charts-color-functional-negative)',
620620
},
621621
}));
622622

docs/src/pages/[version]/examples/index.astro

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,20 @@ export function getStaticPaths() { return [{ params: { version: version } }] }
129129
</div>
130130
</div>
131131
</div>
132+
<div class="col-12 col-lg-4 col-md-6 align-self-stretch py-2">
133+
<div class="card h-100">
134+
<img src={`/${version}/images/ods-geographic-charts.png`} class="card-img-top" alt="" />
135+
<div class="card-body">
136+
<h5 class="card-title">Orange Design System</h5>
137+
<h6 class="card-subtitle">Geographic data</h6>
138+
<p class="card-text">Uses a map to show trends or analyse the distribution of data over a geographical region.</p>
139+
<a href="https://unified-design-system.orange.com/472794e18/p/33b7f8-geographic-data" target="_blank" class="card-link">Orange Design System - Data display</a>
140+
</div>
141+
<div class="card-footer">
142+
<a href="./geographic-data-chart/" class="btn btn-primary mt-3">Go to example</a>
143+
</div>
144+
</div>
145+
</div>
132146
</div>
133147
</div>
134148
</BaseLayout>

docs/static/[version]/examples.js

Lines changed: 220 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ async function displayChart(
319319
while (!(iframe.contentWindow.boosted && iframe.contentWindow.ODSCharts && iframe.contentWindow.echarts)) {
320320
await wait(50);
321321
}
322+
await loadMaps(iframe.contentWindow.echarts);
322323

323324
if (document.querySelector('[data-bs-theme]')) {
324325
iframe.contentDocument.body.setAttribute('data-bs-theme', document.querySelector('[data-bs-theme]').getAttribute('data-bs-theme'));
@@ -471,8 +472,7 @@ async function displayChart(
471472

472473
legends =
473474
usedLegends === 'odscharts' &&
474-
(hasLegend ||
475-
(options.legend && options.legend.data && !options.legend.show) ||
475+
((options.legend && options.legend.data && !options.legend.show) ||
476476
(options.dataset && options.dataset.source) ||
477477
(options.series && 1 === options.series.length && 'pie' === options.series[0].type));
478478

@@ -482,7 +482,7 @@ async function displayChart(
482482
iframe.contentDocument.getElementById(id + '_holder_with_legend').style.flexDirection = 'column';
483483
}
484484

485-
if (!legends && usedLegends === 'odscharts') {
485+
if ((!legends && !hasLegend && usedLegends === 'odscharts') || chartConfigMethod.search(/MapChart/)) {
486486
document.querySelectorAll(`#accordion_${id} .legends-style`).forEach((elt) => {
487487
elt.style.display = 'none';
488488
});
@@ -685,7 +685,7 @@ myChart.setOption(themeManager.getChartOptions());
685685
elt.style.display = iframe.contentWindow.ODSCharts.ODSChartsCSSThemesNames.NONE === cssThemeName || 'none' === popoverInput ? 'none' : 'block';
686686
});
687687
document.querySelectorAll(`#accordion_${id} .popover-config`).forEach((elt) => {
688-
elt.style.display = 'none' === popoverInput ? 'none' : 'block';
688+
elt.style.display = 'none' === popoverInput || chartConfigMethod.search(/MapChart/) ? 'none' : 'block';
689689
});
690690
document.querySelectorAll(`#accordion_${id} .line-style-config`).forEach((elt) => {
691691
elt.style.display = -1 === chartConfigMethod.search(/Line/) ? 'none' : 'block';
@@ -1225,3 +1225,219 @@ window.generateHorizontalGaugeChart = async (id) => {
12251225
};
12261226
displayChart('getHorizontalGaugeChartConfiguration', id, option, undefined, [{ colorPalette: ODSCharts.ODSChartsColorsSet.OUDS_CATEGORICAL, colorIndex: 4 }]);
12271227
};
1228+
1229+
// const worldMap = async () => {
1230+
// // TODO: License
1231+
// 1Mo
1232+
// License: https://mapsvg.com/maps/world
1233+
// return await fetch(`/0.4/images/maps/world.svg`).then((response) => response.text());
1234+
// };
1235+
1236+
// 1Mo
1237+
// No License so far
1238+
const worldMap = async () => {
1239+
return await fetch('https://raw.githubusercontent.com/apache/echarts-website/refs/heads/asf-site/examples/data/asset/geo/world.json').then((response) =>
1240+
response.json()
1241+
);
1242+
};
1243+
1244+
// 250Ko
1245+
// License: Unlicense
1246+
// const worldMap = async () => {
1247+
// return await fetch('https://raw.githubusercontent.com/johan/world.geo.json/refs/heads/master/countries.geo.json').then((response) => response.json());
1248+
// };
1249+
1250+
// 14Mo
1251+
// License: https://opendatacommons.org/licenses/pddl/1-0/
1252+
// const worldMap = async () => {
1253+
// return await fetch('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson').then((response) => response.json());
1254+
// };
1255+
1256+
// License to ask for the 2 following
1257+
const franceDeps = async () => {
1258+
return await fetch('https://raw.githubusercontent.com/gregoiredavid/france-geojson/refs/heads/master/departements-version-simplifiee.geojson')
1259+
.then((response) => response.json())
1260+
.then((json) => JSON.parse(JSON.stringify(json).replaceAll('nom', 'name')));
1261+
};
1262+
1263+
const franceRegions = async () => {
1264+
// https://adresse.data.gouv.fr/data/contours-administratifs/latest/geojson/regions-1000m.geojson
1265+
return await fetch('https://raw.githubusercontent.com/gregoiredavid/france-geojson/refs/heads/master/regions-version-simplifiee.geojson')
1266+
.then((response) => response.json())
1267+
.then((json) => JSON.parse(JSON.stringify(json).replaceAll('nom', 'name')));
1268+
};
1269+
1270+
const loadMaps = async (echarts) => {
1271+
let worldMapGeoJson = await worldMap();
1272+
// let franceDepsGeoJson = await franceDeps();
1273+
let franceRegionsGeoJson = await franceRegions();
1274+
1275+
// Register the map with ECharts if loaded
1276+
if (worldMapGeoJson && typeof echarts !== 'undefined') {
1277+
// echarts.registerMap('world', { svg: worldMapGeoJson });
1278+
echarts.registerMap('world', worldMapGeoJson);
1279+
}
1280+
1281+
// if (franceDepsGeoJson && typeof echarts !== 'undefined') {
1282+
// echarts.registerMap('france-deps', franceDepsGeoJson);
1283+
// }
1284+
1285+
if (franceRegionsGeoJson && typeof echarts !== 'undefined') {
1286+
echarts.registerMap('france-regions', franceRegionsGeoJson);
1287+
}
1288+
};
1289+
1290+
window.generateChoroplethMapChart = async (id) => {
1291+
// Sample data for choropleth map - using regions map (built-in)
1292+
const mapData = [
1293+
{ name: 'Auvergne-Rhône-Alpes', value: 8260 },
1294+
{ name: 'Bourgogne-Franche-Comté', value: 2793 },
1295+
{ name: 'Bretagne', value: 3475 },
1296+
{ name: 'Centre-Val de Loire', value: 2581 },
1297+
{ name: 'Corse', value: 360 },
1298+
{ name: 'Grand Est', value: 5544 },
1299+
{ name: 'Hauts-de-France', value: 5973 },
1300+
{ name: 'Île-de-France', value: 12450 },
1301+
{ name: 'Normandie', value: 3341 },
1302+
{ name: 'Nouvelle-Aquitaine', value: 6191 },
1303+
{ name: 'Occitanie', value: 6201 },
1304+
{ name: 'Pays de la Loire', value: 3936 },
1305+
{ name: "Provence-Alpes-Côte d'Azur", value: 5241 },
1306+
];
1307+
1308+
// Specify the configuration items and data for the chart
1309+
var option = {
1310+
visualMap: {
1311+
min: 0,
1312+
max: 12450,
1313+
splitNumber: 4,
1314+
text: ['Population (in thousands)'],
1315+
},
1316+
series: [
1317+
{
1318+
name: 'Population (thousands)',
1319+
type: 'map',
1320+
map: 'france-regions',
1321+
data: mapData,
1322+
},
1323+
],
1324+
};
1325+
1326+
displayChart('getChoroplethMapChartConfiguration', id, option, undefined, ODSCharts.ODSChartsColorsSet.OUDS_SEQUENTIAL_BLUE);
1327+
};
1328+
1329+
window.generateBubbleMapChart = async (id) => {
1330+
// Données des principales économies mondiales
1331+
// Format: [longitude, latitude, PIB en trillions USD, population en millions, nom du pays]
1332+
const bubbleData = [
1333+
// Amérique du Nord
1334+
[-95.7129, 37.0902, 21.43, 331, 'États-Unis'],
1335+
[-106.3468, 56.1304, 1.74, 38, 'Canada'],
1336+
[-102.5528, 23.6345, 1.29, 128, 'Mexique'],
1337+
1338+
// Europe
1339+
[10.4515, 51.1657, 3.85, 83, 'Allemagne'],
1340+
[2.2137, 46.2276, 2.94, 65, 'France'],
1341+
[-3.436, 55.3781, 2.83, 67, 'Royaume-Uni'],
1342+
[12.5674, 41.8719, 2.11, 60, 'Italie'],
1343+
[-3.7492, 40.4637, 1.39, 47, 'Espagne'],
1344+
[19.1451, 51.9194, 0.59, 38, 'Pologne'],
1345+
[31.1656, 48.3794, 0.2, 44, 'Ukraine'],
1346+
[37.6173, 55.7558, 1.48, 146, 'Russie'],
1347+
1348+
// Asie
1349+
[104.1954, 35.8617, 14.34, 1439, 'Chine'],
1350+
[138.2529, 36.2048, 4.94, 126, 'Japon'],
1351+
[127.7669, 35.9078, 1.81, 52, 'Corée du Sud'],
1352+
[77.1025, 28.7041, 3.18, 1380, 'Inde'],
1353+
[113.9213, -0.7893, 1.16, 273, 'Indonésie'],
1354+
[100.9925, 15.87, 0.54, 70, 'Thaïlande'],
1355+
[108.2772, 14.0583, 0.36, 97, 'Vietnam'],
1356+
[121.774, 12.8797, 0.38, 109, 'Philippines'],
1357+
[101.9758, 4.2105, 0.37, 32, 'Malaisie'],
1358+
[103.8198, 1.3521, 0.34, 6, 'Singapour'],
1359+
1360+
// Moyen-Orient
1361+
[53.8478, 23.4241, 0.7, 35, 'Arabie Saoudite'],
1362+
[51.1839, 35.6892, 0.23, 83, 'Iran'],
1363+
[35.2444, 31.0461, 0.48, 9, 'Israël'],
1364+
[54.3773, 24.4539, 0.51, 10, 'Émirats Arabes Unis'],
1365+
1366+
// Afrique
1367+
[22.9375, -30.5595, 0.42, 59, 'Afrique du Sud'],
1368+
[30.8025, 26.8206, 0.4, 102, 'Égypte'],
1369+
[-1.0232, 7.9465, 0.07, 31, 'Ghana'],
1370+
[37.9062, -0.0236, 0.11, 53, 'Kenya'],
1371+
[17.8739, 13.5317, 0.05, 206, 'Nigeria'],
1372+
1373+
// Amérique du Sud
1374+
[-51.9253, -14.235, 1.61, 212, 'Brésil'],
1375+
[-58.3816, -34.6037, 0.45, 45, 'Argentine'],
1376+
[-74.2973, 4.5709, 0.31, 50, 'Colombie'],
1377+
[-76.0152, -9.19, 0.2, 33, 'Pérou'],
1378+
[-66.5897, -16.2902, 0.04, 12, 'Bolivie'],
1379+
1380+
// Océanie
1381+
[133.7751, -25.2744, 1.55, 25, 'Australie'],
1382+
[174.886, -40.9006, 0.25, 5, 'Nouvelle-Zélande'],
1383+
];
1384+
1385+
// Conversion des données pour ECharts
1386+
const scatterData = bubbleData.map((item) => ({
1387+
name: item[4],
1388+
value: [item[0], item[1], item[2], item[3]], // [lng, lat, PIB, population]
1389+
symbolSize: Math.sqrt(item[2]) * 15, // Taille basée sur le PIB
1390+
}));
1391+
1392+
const option = {
1393+
tooltip: {
1394+
trigger: 'item',
1395+
formatter: (params) => {
1396+
params = params[0];
1397+
if (params.seriesType === 'scatter') {
1398+
const data = params.value;
1399+
return `
1400+
<strong>${params.name}</strong><br/>
1401+
PIB: ${data[2]} trillions USD<br/>
1402+
Population: ${data[3]} millions<br/>
1403+
Coordonnées: ${data[0].toFixed(2)}°, ${data[1].toFixed(2)}°`;
1404+
}
1405+
return params.name;
1406+
},
1407+
},
1408+
geo: {
1409+
map: 'world',
1410+
zoom: 1.2,
1411+
center: [0, 15],
1412+
},
1413+
grid: {
1414+
top: 0,
1415+
right: 100,
1416+
},
1417+
series: [
1418+
{
1419+
name: 'Pays',
1420+
type: 'scatter',
1421+
data: scatterData,
1422+
symbolSize: function (val) {
1423+
return Math.sqrt(val[2]) * 12; // Taille basée sur le PIB
1424+
},
1425+
},
1426+
],
1427+
visualMap: {
1428+
min: 0,
1429+
max: 1500,
1430+
dimension: 3, // Population (4ème valeur dans value array)
1431+
pieces: [
1432+
{ min: 0, max: 50 },
1433+
{ min: 50, max: 100 },
1434+
{ min: 100, max: 200 },
1435+
{ min: 200, max: 800 },
1436+
{ min: 800, max: 1500 },
1437+
],
1438+
text: ['Population'],
1439+
},
1440+
};
1441+
1442+
displayChart('getBubbleMapChartConfiguration', id, option, undefined, ODSCharts.ODSChartsColorsSet.OUDS_SEQUENTIAL_BLUE);
1443+
};
34.9 KB
Loading

0 commit comments

Comments
 (0)