Fermer

mars 7, 2023

Comment créer des graphiques en anneau dynamiques avec TailwindCSS et réagir

Comment créer des graphiques en anneau dynamiques avec TailwindCSS et réagir


CSS est incroyable – je suis régulièrement surpris de voir à quel point il a évolué au cours des années où je l’ai utilisé (~ 2005 – présent). Une de ces surprises est venue lorsque j’ai remarqué ce tweet de Shruti Balasa qui a montré comment créer un graphique à secteurs en utilisant conic-gradient().

C’est assez simple. Voici un extrait de code :

div {
  background: conic-gradient(red 36deg, orange 36deg 170deg, yellow 170deg);
  border-radius: 50%;
}

En utilisant cette petite quantité de CSS, vous pouvez créer des dégradés qui commencent et s’arrêtent à des angles spécifiques et définir une couleur pour chaque « segment » du graphique à secteurs.

Graphiques à gradient conique CSS avec des graphiques en anneau et un graphique à secteurs
(Grand aperçu)

Jours heureux!

Brills, j’ai pensé que je pourrais l’utiliser à la place d’une bibliothèque de graphiques pour un projet de tableau de bord de données sur lequel je travaille pour le nouveau API Cloud CockroachDB, mais j’ai eu un problème. Je ne connaissais pas les valeurs de mon graphique à l’avance, et les valeurs que je recevais de l’API n’étaient pas en degrés !

Voici un lien de prévisualisation et un dépôt open-source de la façon dont j’ai travaillé autour de ces deux problèmes, et dans le reste de cet article, j’expliquerai comment tout cela fonctionne.

Valeurs de données dynamiques

Voici quelques exemples de données d’un typique Réponse API que j’ai triée par value.

const data = [
  {
    name: 'Cluster 1',
    value: 210,
  },
  {
    name: 'Cluster 2',
    value: 30,
  },
  {
    name: 'Cluster 3',
    value: 180,
  },
  {
    name: 'Cluster 4',
    value: 260,
  },
  {
    name: 'Cluster 5',
    value: 60,
  },
].sort((a, b) => a.value - b.value);

Vous pouvez voir que chaque élément du tableau a un name et un value.

Afin de convertir le value d’un nombre à un deg valeur à utiliser avec CSS, il y a quelques choses que vous devez faire :

  • Calculez le montant total de toutes les valeurs.
  • Utilisez le montant total pour calculer le pourcentage que chaque valeur représente.
  • Convertissez le pourcentage en degrés.

Note: Le code auquel je ferai référence dans les étapes ci-dessous peut être trouvé dans le référentiel ici: /composants/donut-1.js.

Plus après saut! Continuez à lire ci-dessous ↓

Calculer le montant total

En utilisant JavaScript, vous pouvez utiliser ce petit one-liner pour somme chaque valeur du tableau de données, ce qui donne un seul total.

const total_value = data.reduce((a, b) => a + b.value, 0);

// => 740

Calculer le pourcentage

Maintenant que vous avez un total_value, vous pouvez convertir chacune des valeurs du tableau de données en pourcentage à l’aide d’une fonction JavaScript. j’ai appelé cette fonction covertToPercent.

Note: J’ai utilisé la valeur de 210 du cluster 1 dans cet exemple.

const convertToPercent = (num) => Math.round((num / total_value) * 100);

// convertToPercent(210) => 28

Convertir un pourcentage en degrés

Une fois que vous avez un pourcentage, vous pouvez convertir le pourcentage en degrés en utilisant une autre fonction JavaScript. j’ai appelé cette fonction convertToDegrees.

const convertToDegrees = (num) => Math.round((num / 100) * 360);

// convertToDegrees(28) => 101

Le résultat

Comme test temporaire, si je devais carte sur les éléments du tableau de données triées, en utilisant les deux fonctions expliquées ci-dessus, vous obtiendrez la sortie suivante :

const test_output = data.map((item) => {
  const percentage = convertToPercent(item.value);
  const degrees = convertToDegrees(percentage);

  return `${degrees}deg`;
});

// => ['14deg', '29deg', '86deg', '101deg', '126deg']

La valeur de retour de test_output est un tableau de value (en degrés) + la chaîne deg.

Cela résout l’un d’un problème en deux parties. Je vais maintenant vous expliquer l’autre partie du problème.

Pour créer un graphique à secteurs à l’aide de conic-gradient()il te faut deux deg valeurs. Le premier est l’angle à partir duquel le dégradé doit commencer et le second est l’angle où le dégradé doit s’arrêter. Vous aurez également besoin d’une couleur pour chaque segment, mais j’y reviendrai dans un instant.

 ['red 🤷 14deg', 'blue 🤷 29deg', 'green 🤷 86deg', 'orange 🤷 101deg', 'pink 🤷 126deg']

En utilisant les valeurs du test_output, je n’ai que la valeur finale (où le dégradé doit s’arrêter). L’angle de départ de chaque segment est en fait l’angle de fin de l’élément précédent dans le tableau, et l’angle de fin est la valeur cumulée de toutes les valeurs de fin précédentes plus la valeur de fin actuelle. Et pour aggraver les choses, la valeur de départ du premier angle doit être réglée manuellement sur 0 🥴.

Voici un schéma pour mieux expliquer ce que cela signifie :

Un schéma qui explique une fonction
(Grand aperçu)

Si cela semble déroutant, c’est parce que c’est le cas, mais si vous regardez la sortie d’une fonction qui peut faire tout cela, cela pourrait avoir plus de sens.

"#...", 0, 14,
"#...",, 14, 43,
"#...",, 43, 130,
"#...",, 130, 234,
"#...",, 234, 360,

La fonction qui peut faire tout cela

Et voici la fonction qui peut en effet faire tout cela. Il utilise reduce() pour itérer sur le tableau de données, effectue l’addition nécessaire pour calculer les angles et renvoie un nouvel ensemble de nombres qui peuvent être utilisés pour créer les angles de début et de fin corrects à utiliser dans un graphique.

const total_value = data.reduce((a, b) => a + b.value, 0);
const convertToPercent = (num) => Math.round((num / total_value) * 100);
const convertToDegrees = (num) => Math.round((num / 100) * 360);

const css_string = data
  .reduce((items, item, index, array) => {
    items.push(item);

    item.count = item.count || 0;
    item.count += array[index - 1]?.count || item.count;
    item.start_value = array[index - 1]?.count ? array[index - 1].count : 0;
    item.end_value = item.count += item.value;
    item.start_percent = convertToPercent(item.start_value);
    item.end_percent = convertToPercent(item.end_value);
    item.start_degrees = convertToDegrees(item.start_percent);
    item.end_degrees = convertToDegrees(item.end_percent);

    return items;
  }, [])
  .map((chart) => {
    const { color, start_degrees, end_degrees } = chart;
    return ` ${color} ${start_degrees}deg ${end_degrees}deg`;
  })
  .join();

J’ai délibérément laissé ce joli verbeux, il est donc plus facile d’ajouter console.log(). J’ai trouvé cela très utile lorsque j’ai développé cette fonction.

Vous remarquerez peut-être le supplément map enchaîné au bout du reduce. En utilisant un map Je suis capable de modifier les valeurs retournées et de virer sur degpuis renvoyez-les tous ensemble sous la forme d’un tableau de chaînes.

En utilisant join juste à la fin reconvertit le tableau en un seul css_stringqui peut être utilisé avec conic-gradient() 😅.

"#..." 0deg 14deg,
"#..." 14deg 43deg,
"#..." 43deg 130deg,
"#..." 130deg 234deg,
"#..." 234deg 360deg

En utilisant le css_string Avec un SVG foreignObject

Maintenant, malheureusement, vous ne pouvez pas utiliser conic-gradient() avec SVG. Mais vous pouvez envelopper un élément HTML dans un foreignObject et stylisez le background utilisant un conic-gradient().

<svg viewBox='0 0 100 100' xmlns="http://www.w3.org/2000/svg" style={{ borderRadius: '100%' }}>
  <foreignObject x='0' y='0' width="100" height="100">
    <div
      xmlns="http://www.w3.org/1999/xhtml"
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`, // <- 🥳
      }}
    />
  </foreignObject>
</svg>

En utilisant ce qui précède, vous devriez regarder un graphique à secteurs. Afin de faire un graphique en anneau, je vais devoir expliquer comment faire le trou.

Parlons du trou

Il n’y a vraiment qu’une seule façon de « masquer » le milieu du graphique à secteurs pour révéler l’arrière-plan. Cette approche consiste à utiliser un clipPath. Cette approche ressemble à l’extrait de code ci-dessous. Je l’ai utilisé pour Donut 1.

Note: Le src pour Donut 1 peut être vu ici : composants/donut-1.js.

<svg viewBox='0 0 100 100' xmlns="http://www.w3.org/2000/svg" style={{ borderRadius: '100%' }}>

  <clipPath id='hole'>
    <path d='M 50 0 a 50 50 0 0 1 0 100 50 50 0 0 1 0 -100 v 18 a 2 2 0 0 0 0 64 2 2 0 0 0 0 -64' />
  </clipPath>

  <foreignObject x='0' y='0' width="100" height="100" clipPath="url(#hole)">
    <div
      xmlns="http://www.w3.org/1999/xhtml"
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`
      }}
    />
  </foreignObject>
</svg>

Cependant, il existe un autre moyen. Cette approche consiste à utiliser un <circle /> élément et en le plaçant au centre du graphique à secteurs. Cela fonctionnera si le remplissage du <circle /> correspond à la couleur d’arrière-plan de tout ce sur quoi le graphique est placé. Dans mon exemple, j’ai utilisé un arrière-plan à motif, et vous remarquerez si vous regardez attentivement Donut 3 que vous ne pouvez pas voir le motif à bulles passant par le centre du graphique.

Note: Le src pour Donut 3 peut être vu ici : composants/donut-3.js.

<svg viewBox='0 0 100 100' xmlns="http://www.w3.org/2000/svg" style={{ borderRadius: '100%' }}>
  <foreignObject x='0' y='0' width="100" height="100">
    <div
      xmlns="http://www.w3.org/1999/xhtml"
      style={{
        width: '100%',
        height: '100%',
        background: `conic-gradient(${css_string})`
      }}
    />
  </foreignObject>
  <circle cx='50' cy='50' r="32" fill="white" />
</svg>

OMI le clipPath L’approche est plus agréable, mais il peut être plus difficile de modifier les points du chemin pour obtenir l’épaisseur souhaitée du trou si vous n’avez pas accès à quelque chose comme Figma ou Illustrator.

Enfin, les couleurs !

Les couleurs pour les graphiques sont quelque chose qui me pose toujours des problèmes. La plupart du temps, les couleurs que j’utilise sont définies en CSS, et tout cela se passe en JavaScript, alors comment utiliser les variables CSS en JavaScript ?

Dans mon exemple de site, j’utilise Vent arrière pour styliser ‘toutes les choses’ et en utilisant cette astuceje peux exposer les variables CSS afin qu’elles puissent être référencées par leur nom.

Si vous voulez faire la même chose, vous pouvez ajouter un color clé du tableau de données :

data={[
  {
    name: 'Cluster 1',
    value: 210,
    color: 'var(--color-fuchsia-400)',
  },
  {
    name: 'Cluster 2',
    value: 30,
    color: 'var(--color-fuchsia-100)',
  },
  {
    name: 'Cluster 3',
    value: 180,
    color: 'var(--color-fuchsia-300)',
  },
  {
    name: 'Cluster 4',
    value: 260,
    color: 'var(--color-fuchsia-500)',
  },
  {
    name: 'Cluster 5',
    value: 60,
    color: 'var(--color-fuchsia-200)',
  },
].sort((a, b) => a.value - b.value)

Et puis référencez le color clé dans le tableau map pour le retourner dans le cadre du css_string. J’ai utilisé cette approche dans Donut 2.

Note: Tu peux voir le src pour Donut 2 ici : composants/donut-2.js.

.map((chart) => {
  const { color, start_degrees, end_degrees } = chart;
  return ` ${color} ${start_degrees}deg ${end_degrees}deg`;
})
.join();

Vous pouvez même créer dynamiquement le nom de la couleur en utilisant une valeur codée en dur (color-pink-) + le index du tableau. J’ai utilisé cette approche dans Donut 1.

Note: Tu peux voir le src pour Donut 1 ici : composants/donut-1.js.

.map((chart, index) => {
  const { start_degrees, end_degrees } = chart;
  return ` var(--color-pink-${(index + 1) * 100}) ${start_degrees}deg ${end_degrees}deg`;
})
.join();

Si tu es chanceux!

Cependant, vous pourriez avoir de la chance et travailler avec une API qui renvoie en fait des valeurs avec une couleur associée. C’est le cas avec le API GitHub GraphQL. Donc. J’ai sauté ensemble un dernier exemple.

API GitHub GraphQL avec graphique Github avec dix langues différentes associées à sa propre couleur
(Grand aperçu)

Vous pouvez voir cela fonctionner dans votre navigateur en visitant /githubet le src pour le graphique GitHub Donut et la légende peuvent être trouvés ici :

Emballer

Vous pensez peut-être que c’est assez compliqué et qu’il est probablement plus facile d’utiliser une bibliothèque de graphiques, et vous avez probablement raison. C’est probablement le cas. Mais cette façon est super léger. Il n’y a pas de bibliothèques supplémentaires à installer ou à maintenir, et il n’y a pas de JavaScript lourd qui doit être téléchargé par le navigateur pour qu’ils fonctionnent.

J’ai expérimenté une fois auparavant la création de graphiques en anneau en utilisant un SVG et le stroke-dashoffset. Vous pouvez lire à ce sujet dans mon article, « Créez un graphique en anneau SVG à partir de zéro pour votre blog Gatsby.” Cette approche a très bien fonctionné, mais je pense que je préfère l’approche décrite dans ce post. CSS est tout simplement le meilleur !

Si vous souhaitez discuter de l’une des méthodes que j’ai utilisées ici, venez me trouver sur Twitter : @PaulieScanlon.

À bientôt sur Internet !

Éditorial fracassant
(yk, il)






Source link