Ocultando texto en porciónb gráfica de tarta

Mostrar un texto centrado en cada porción está bien, pero ¿Qué pasa si no hay espacio para mostrar ese texto? Si nos ponemos en el caso de que una porción es demasiado pequeña, puede quedar bastante mal intentar mostrar un texto, vamos a ver como arreglar ésto.

Manos a la obra

Primero cambiemos los datos y veamos el problema que tenemos

const ventas: Ventas[] = [
-  { mes: "Enero", ventas: 120 },
+  { mes: "Enero", ventas: 5200 },
  { mes: "Febrero", ventas: 2500 },
  { mes: "Marzo", ventas: 3000 },
];

Cuando mostramos la gráfica podemos ver que no hay espacio para mostrar la etiqueta del mes de enero.

Lo ideal sería que si no tenemos espacio no se muestre la etiqueta, ¿Cómo podemos hacerlo?

  • Vamos a crear un interfaz que defina un punto:
interface Point {
  x: number;
  y: number;
}
  • Implementamos una función que compruebe si un punto está dentro de un arco.

En esta función le pasamos el punto que queremos comprobar, el arco en concreto y la funciona generadora de arco, y aplicando un poco de mates sacamos si el putno está dentro o no, en las referencias de este post encontrarás un enlace con más información acerca de esto.

function estaElPuntoEnElArco(
  punto: Point,
  arco: d3.DefaultArcObject,
  d3Arc: d3.Arc<any, d3.DefaultArcObject>
) {
  // Se asume que el centro del arco es 0,0
  // (pt.x, pt.y) se asume que son relativos al centro
  const r1 = d3Arc.innerRadius()(arco),
    r2 = d3Arc.outerRadius()(arco),
    theta1 = d3Arc.startAngle()(arco),
    theta2 = d3Arc.endAngle()(arco);

  const dist = punto.x * punto.x + punto.y * punto.y;
  let angle = Math.atan2(punto.x, -punto.y);

  angle = angle < 0 ? angle + Math.PI * 2 : angle;

  return (
    r1 * r1 <= dist && dist <= r2 * r2 && theta1 <= angle && angle <= theta2
  );
}
  • Segundo, del texto que queremos mostrar vamos a sacar las coordenadas del rectangulo que ocuparía en pantalla, para ello vamos a:

    • Tirar de la función each que me permite acceder al elemento del dom que se esta generando.
    • Calculamos con la función getBBox (get bounding box), el tamaño que ocupa el texto, y sacamos el centroide del arco.
  .style("font-size", 17)
+  .each(function (d: any) {
+    const bb = this.getBBox();
+    const center = constructorDePath.centroid(<any>d);
  • Sacamos cada esquina del rectangulo.
    const bb = this.getBBox();
    const center = constructorDePath.centroid(<any>d);

+    const topLeft: Point = {
+      x: center[0] + bb.x,
+      y: center[1] + bb.y,
+    };
+
+    const topRight: Point = {
+      x: topLeft.x + bb.width,
+      y: topLeft.y,
+    };
+
+    const bottomLeft: Point = {
+      x: topLeft.x,
+      y: topLeft.y + bb.height,
+    };
+
+    const bottomRight: Point = {
+      x: topLeft.x + bb.width,
+      y: topLeft.y + bb.height,
+    };
  • Llamamos a la función EstaElPuntoEnArco para ver si cada uno de las esquinas está dentro del mismo, El resultado de esas llamadas se lo asignamos a una propiedad que añadimos al datum, la llamaremos visible
    const bottomRight: Point = {
      x: topLeft.x + bb.width,
      y: topLeft.y + bb.height,
    };

+    d.visible =
+      estaElPuntoEnElArco(topLeft, d, constructorDePath) &&
+      estaElPuntoEnElArco(topRight, d, constructorDePath) &&
+      estaElPuntoEnElArco(bottomLeft, d, constructorDePath) &&
+      estaElPuntoEnElArco(bottomRight, d, constructorDePath);
+  })
  • Ya que tenemos esa propiedad calculada, vamos a aplicar un estilo, si la propiedad visible de la figura asociada al dato que vayamos a pintar esta a false no mostramos el texto.
      EstaElPuntoEnElArco(bottomRight, d, constructorDePath);
  })
+  .style("display", function (d) {
+    return d["visible"] ? null : "none";
+  });

Ahora podemos jugar con distintos datos y veremos que si una o varias porciones no tienen espacio suficiente el texto no se mostrara.

Referencias

Este ejemplo está basado en el ejemplo descrito en este enlace: https://stackoverflow.com/questions/19792552/d3-put-arc-labels-in-a-pie-chart-if-there-is-enough-space

¿Te apuntas a nuestro máster?

Si te ha gustado este ejemplo y tienes ganas de aprender Front End guiado por un grupo de profesionales ¿Por qué no te apuntas a nuestro Máster Front End Online Lemoncode? Tenemos tanto edición de convocatoria con clases en vivo, como edición continua con mentorización, para que puedas ir a tu ritmo y aprender mucho.