Contexte et objectif
Implémenter une fonctionnailté où le survol d'un élément déclenche l'affichage d'une infobulle contenant un graphique Echarts, dont les données se mettent à jour dynamiquement.
Cette solution s'appuie sur Vue.js 2 et la bibliothèque Echarts. L'architecture suppose une hiérarchie de composants : un composant parent gère l'état des données, un composant intermédiaire gère l'interaction et la logique d'affichage, et un composant enfant est dédié au rendu du graphique.
Architecture de gestion des données
Le composant parent centralise les données et les transmet aux enfants. La communication descendante s'effectue via les props, tandis qu'une méthode exposée permet au parent d'initialiser le lien avec l'enfant.
<!-- Composant Parent -->
<template>
<div id="app">
<ComposantIntermediaire ref="refComposant"></ComposantIntermediaire>
</div>
</template>
<script>
import ComposantIntermediaire from './ComposantIntermediaire.vue';
export default {
name: 'App',
components: { ComposantIntermediaire },
data() {
return {
donneesGlobales: {
serieA: null,
serieB: null
}
};
},
mounted() {
this.initialiserComposants();
},
methods: {
initialiserComposants() {
const instance = this.$refs.refComposant;
if (instance) {
instance.lierDonneesPartagees(this.donneesGlobales);
}
}
}
};
</script>
Copmosant intermédiaire : interaction et liaison
Ce composant écoute les événements de souris sur des zones définies. Il positionne dynamiquement l'infobulle et orchestre la mise à jour du graphique enfant lorsque les données partagées changent.
<!-- ComposantIntermediaire.vue -->
<template>
<div class="zone-graphique">
<div
class="gachette"
@mouseenter="afficherGraphiqueBulle($event, 'A')"
@mouseleave="masquerGraphiqueBulle"
>
Zone A
</div>
<div
class="gachette"
@mouseenter="afficherGraphiqueBulle($event, 'B')"
@mouseleave="masquerGraphiqueBulle"
>
Zone B
</div>
<div
class="infobulle"
v-show="infobulleVisible"
:style="{ left: positionX + 'px', top: positionY + 'px' }"
>
<GraphiqueEcharts ref="refGraphique"></GraphiqueEcharts>
</div>
</div>
</template>
<script>
import GraphiqueEcharts from './GraphiqueEcharts.vue';
export default {
name: 'ComposantIntermediaire',
components: { GraphiqueEcharts },
data() {
return {
typeSelectionne: null,
infobulleVisible: false,
positionX: 0,
positionY: 0,
donneesGraphique: null,
donneesPartagees: {
serieA: { label: 'Catégorie A', valeurs: [10, 25, 18, 30, 22] },
serieB: { label: 'Catégorie B', valeurs: [15, 8, 22, 12, 28] }
}
};
},
watch: {
donneesPartagees: {
handler(nouvellesDonnes) {
if (nouvellesDonnes && this.infobulleVisible) {
this.mettreAJourDonneesGraphique();
}
},
deep: true
}
},
methods: {
lierDonneesPartagees(data) {
if (data) {
this.donneesPartagees = data;
}
},
afficherGraphiqueBulle(event, type) {
this.typeSelectionne = type;
this.mettreAJourDonneesGraphique();
const elementCible = event.currentTarget;
const rect = elementCible.getBoundingClientRect();
this.positionX = rect.left;
this.positionY = rect.bottom + 10;
this.infobulleVisible = true;
},
masquerGraphiqueBulle() {
this.infobulleVisible = false;
this.donneesGraphique = null;
const graphique = this.$refs.refGraphique;
if (graphique) {
graphique.viderGraphique();
}
},
mettreAJourDonneesGraphique() {
const source = this.typeSelectionne === 'A' ?
this.donneesPartagees.serieA :
this.donneesPartagees.serieB;
this.donneesGraphique = source;
const graphique = this.$refs.refGraphique;
if (graphique) {
graphique.initialiserGraphique(this.donneesGraphique);
}
}
}
};
</script>
Composant anfant : le graphique Echarts
Ce composant encapsule l'instance Echarts. Il expose des méthodes pour l'initialisation, le rendu des données et le nettoyage des ressources.
<!-- GraphiqueEcharts.vue -->
<template>
<div class="conteneur-echarts">
<div ref="elementGraphique"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'GraphiqueEcharts',
data() {
return {
instanceGraphique: null,
donneesRecues: null
};
},
beforeDestroy() {
this.nettoyerGraphique();
window.removeEventListener('resize', this.redimensionner);
},
methods: {
initialiserGraphique(donnees) {
if (!donnees) return;
if (!this.instanceGraphique) {
this.instanceGraphique = echarts.init(this.$refs.elementGraphique);
window.addEventListener('resize', this.redimensionner);
}
this.donneesRecues = donnees;
this.construireGraphique(this.donneesRecues.valeurs);
},
construireGraphique(valeursY) {
const categoriesX = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
const option = {
backgroundColor: '#2c343c',
xAxis: {
type: 'category',
data: categoriesX,
axisLabel: { color: '#aaa' }
},
yAxis: {
type: 'value',
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { type: 'dashed', color: '#444' } }
},
series: [{
name: this.donneesRecues.label,
type: 'line',
data: valeursY,
smooth: true,
areaStyle: { color: 'rgba(64, 158, 255, 0.3)' },
itemStyle: { color: '#409eff' },
lineStyle: { width: 2 }
}]
};
this.instanceGraphique.setOption(option, true);
},
redimensionner() {
if (this.instanceGraphique) {
this.instanceGraphique.resize();
}
},
viderGraphique() {
if (this.instanceGraphique) {
this.instanceGraphique.clear();
this.instanceGraphique.dispose();
this.instanceGraphique = null;
}
},
nettoyerGraphique() {
this.viderGraphique();
}
}
};
</script>