S-014 – Integración Anychart en Svelte

Con Anychart he realizado varios ejemplos (de cierta calidad para mí) y es un producto con el que me encuentro muy cómodo por el gran potencial que tiene, ya que dispone de gráficos , mapas, etc., de todos los tipos y su soporte es excepcional.

Me «tope» con él hace muchos año cuadro empecé a utilizar PHPRunner, aunque su integración era bastante floja, pues se podía hacer muy pocas cosas, lo que sí facilitaba era un código de licencia que te permitía hacer la totalidad funcional del producto.

Es un producto que hay que licenciar, si no dispones de un código válido de otro producto, pero si vas a realizar una APP con un potencial gráfico grande, seguro que te va a ser de gran ayuda.

Objetivo

Analizar la integración de AnyChart con Svelte y verificar su funcionamiento, incluyendo el tema de multi-idioma. Se ha hecho la integración de 2 formas:

  • NPM .- Con la instalación de la librería como cualquier otra de las librerías para los framework de Front-End de JavaScript.
  • CDN.- Con la misma integración que se utiliza para las aplicaciones WEB. Esta opción puede ser muy interesante pues aunque no encuentres documentación del proveedor que funciona en Svelte, lo normal es que puedas integrarlo de forma similar a este ejemplo.

DEMOS:
NPM – https://fhumanes.com/anychart-svelte/
CDN – https://fhumanes.com/anychart2-svelte/

Solución Técnica

Para esta integración me he ayudado del soporte de AnyChart y de la IA Copilot.

En la versión NPM, veréis que la parte principal se instala con este comando de NPM, pero el resto de Idiomas, etc, se deben cargar desde CDN o de tus copias locales. Así se ha hecho para poder poner este ejemplo en Español.

Los 2 ejemplos son iguales y tienen la siguiente funcionalidad:

  • Analizar la funcionalidad reactiva y para ello, en el mismo espacio de la página se visualizan 2 gráficos de aspectos y distintos aunque utilicemos el mismo tipo de gráfico.
  • Debería poder ponerse en el idioma español (para mostrar cómo puede pasar del inglés a cualquier otro idioma).
  • Se han modificado las «label’s» de las distintas porciones.
  • Se han definido los tooltips, ajustándolos a diferentes contenidos en 2 líneas.
  • Se ha representado cómo de forma externa (botón) se puede cambiar atributos del gráfico, en este caso, las leyendas.

Muestro los ficheros del caso NPM.

index.htmlApp.svelte
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- <script src="https://cdn.anychart.com/releases/8.9.0/locales/es-es.js"></script> -->
    <title>anychart-svelte</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
<svelte:head>
  <!-- Carga dinámica de ficheros -->
  <script defer src="./anychart/locales/es-es.js"></script>
</svelte:head>

<script>
  import { onMount } from 'svelte';
  import anychart from 'anychart';

  // Estado reactivo
  let data = $state([]);
  let palette = $state([]);
  let legendPosition = $state('right');

  // Activar locale español (funciona con CDN)
  anychart.format.inputLocale('es-es');
  anychart.format.outputLocale('es-es');

  // Opcional: configuración global de formato (si quieres mantenerla)  Cambio los valores por defecto de LOCALE
  /*
  anychart.format.locales.default.numberLocale.decimalsCount = 2;
  anychart.format.locales.default.numberLocale.zeroFillDecimals = false;
  anychart.format.locales.default.numberLocale.decimalPoint = ",";
  anychart.format.locales.default.numberLocale.groupsSeparator = ".";
  */

  // Dataset A (frutas)
  const datasetA = [
    { x: 'Manzanas', value: 6371664 },
    { x: 'Peras', value: 789622 },
    { x: 'Uvas', value: 7216301 },
    { x: 'Plátanos', value: 1486621 },
    { x: 'Naranjas', value: 1200000 }
  ];

  // Dataset B (vehículos)
  const datasetB = [
    { x: 'Coches', value: 120 },
    { x: 'Motos', value: 80 },
    { x: 'Camiones', value: 40 },
    { x: 'Autobuses', value: 20 }
  ];

  // Paletas personalizadas
  const paletteFrutas = [
    '#FF7043', '#FFA726', '#FFCA28', '#66BB6A', '#42A5F5'
  ];

  const paletteVehiculos = [
    '#546E7A', '#455A64', '#78909C', '#90A4AE'
  ];

  let chart;

  onMount(() => {
    if (typeof anychart === 'undefined') return;

    chart = anychart.pie(data);

    anychart.licenseKey('XXXXXXXXXXXXXXXXXXXXXXX');  // Licencia de PHPRunner

    const credits = chart.credits();
    credits.enabled(false);
    credits.logoSrc('');

    // Leyenda alineada a la derecha por defecto
    chart.legend().position('right');
    chart.legend().itemsLayout('vertical');

    // Formato de etiquetas con tokens
    chart.labels()
    .fontSize(10)
    .fontColor('black')
    .format('{%x}: \n {%Value} \n ({%yPercentOfTotal}{decimalsCount:2}%)'
    );

    var tooltip = chart.tooltip();
    tooltip.useHtml(false);  // por defecto
    tooltip.format("Valor: {%value}\n Porcentaje: {%yPercentOfTotal}{type:number,decimalsCount:2}%");  
    

    chart.title('Ejemplo reactivo con AnyChart + Svelte 5');
    chart.container('anychart-pie-container');
    chart.draw();
  });

  // Reactividad: datos
  $effect(() => {
    if (chart) {
      chart.data(data);
    }
  });

  // Reactividad: paleta
  $effect(() => {
    if (chart) {
      chart.palette(palette);
    }
  });

  // Reactividad: posición de leyenda
$effect(() => {
  if (!chart) return;

  chart.legend().position(legendPosition);

  if (legendPosition === 'bottom') {
    chart.legend().itemsLayout('horizontal');
  } else {
    chart.legend().itemsLayout('vertical');
  }
});


  // Inicialización
  data = datasetA;
  palette = paletteFrutas;
</script>

<section class="page">
  <h1>AnyChart + Svelte 5 (Método NPM)</h1>

  <div class="controls">
    <button onclick={() => { data = datasetA; palette = paletteFrutas; }}>
      Datos de Frutas
    </button>

    <button onclick={() => { data = datasetB; palette = paletteVehiculos; }}>
      Datos de Vehículos
    </button>

    <button onclick={() => legendPosition = 'right'}>
      Leyenda Derecha
    </button>

    <button onclick={() => legendPosition = 'left'}>
      Leyenda Izquierda
    </button>

    <button onclick={() => legendPosition = 'bottom'}>
      Leyenda Abajo
    </button>
  </div>

  <div id="anychart-pie-container" class="chart"></div>
</section>

<style>
  .page {
    display: grid;
    gap: 0.75rem;
    max-width: 720px;
  }

  .controls {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  .chart {
    width: 100%;
    height: 420px;
  }

  button {
    padding: 0.5rem 1rem;
    cursor: pointer;
  }
</style>

Puedes ver que:

  • En el fichero index.html no se carga ninguna librería de JavaScript.
  • En el fichero App.svelte, se puede apreciar cómo se carga el fichero de idioma de Anychart y cómo se gestiona la reactividad de las acciones.

Muestro los ficheros del caso CDN.

index.htmlApp.svelte
<!doctype html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <script defer src="https://cdn.anychart.com/releases/v8/js/anychart-bundle.min.js"></script>
    <!-- Locales -->
    <script defer src="https://cdn.anychart.com/releases/v8/locales/es-es.js"></script>
    <!-- CSS -->
    <link href="https://cdn.anychart.com/releases/v8/css/anychart-ui.min.css" rel="stylesheet">
    <!-- <link href="https://cdn.anychart.com/releases/v8/fonts/css/anychart-font.min.css" rel="stylesheet"> -->

    <title>AnyChart + Svelte</title>
  </head>

  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

 

<script>
  import { onMount } from 'svelte';

  // Estado reactivo
  let data = $state([]);
  let palette = $state([]);
  let legendPosition = $state('right');

  // Dataset A (frutas)
  const datasetA = [
    { x: 'Manzanas', value: 6371664 },
    { x: 'Peras', value: 789622 },
    { x: 'Uvas', value: 7216301 },
    { x: 'Plátanos', value: 1486621 },
    { x: 'Naranjas', value: 1200000 }
  ];

  // Dataset B (vehículos)
  const datasetB = [
    { x: 'Coches', value: 120 },
    { x: 'Motos', value: 80 },
    { x: 'Camiones', value: 40 },
    { x: 'Autobuses', value: 20 }
  ];

  // Paletas personalizadas
  const paletteFrutas = [
    '#FF7043', '#FFA726', '#FFCA28', '#66BB6A', '#42A5F5'
  ];

  const paletteVehiculos = [
    '#546E7A', '#455A64', '#78909C', '#90A4AE'
  ];

  let chart;

  onMount(() => {
    // AnyChart viene del CDN → window.anychart
    if (!window.anychart) return;

    // Activar locale español (funciona con CDN)
    anychart.format.inputLocale('es-es');
    anychart.format.outputLocale('es-es');

    // Configuración numérica adicional
  
    anychart.format.locales.default.numberLocale.decimalsCount = 2;
    anychart.format.locales.default.numberLocale.zeroFillDecimals = false;
    anychart.format.locales.default.numberLocale.decimalPoint = ",";
    anychart.format.locales.default.numberLocale.groupsSeparator = ".";
 

    // Crear gráfico
    chart = window.anychart.pie(data);

    // Licencia (si la tienes)
    window.anychart.licenseKey('xxxxxxxxxxxxxxxxxxxxxxx');

    // Quitar créditos
    const credits = chart.credits();
    credits.enabled(false);
    credits.logoSrc('');

    // Leyenda por defecto
    chart.legend().position('right');
    chart.legend().itemsLayout('vertical');

    // Formato de etiquetas con tokens
    chart.labels()
    .fontSize(10)
    .fontColor('black')
    .format('{%x}: \n {%Value} \n ({%yPercentOfTotal}{decimalsCount:2}%)'
    );

    var tooltip = chart.tooltip();
    tooltip.useHtml(false);  // por defecto
    tooltip.format("Valor: {%value}\n Porcentaje: {%yPercentOfTotal}{type:number,decimalsCount:2}%");   

    chart.title('Ejemplo reactivo con AnyChart + Svelte 5 (CDN)');
    chart.container('anychart-pie-container');
    chart.draw();
  });

  // Reactividad: datos
  $effect(() => {
    if (chart) chart.data(data);
  });

  // Reactividad: paleta
  $effect(() => {
    if (chart) chart.palette(palette);
  });

  // Reactividad: posición de leyenda
  $effect(() => {
    if (!chart) return;

    chart.legend().position(legendPosition);

    if (legendPosition === 'bottom') {
      chart.legend().itemsLayout('horizontal');
    } else {
      chart.legend().itemsLayout('vertical');
    }
  });

  // Inicialización
  data = datasetA;
  palette = paletteFrutas;
</script>

<section class="page">
  <h1>AnyChart + Svelte 5 (Método CDN)</h1>

  <div class="controls">
    <button onclick={() => { data = datasetA; palette = paletteFrutas; }}>
      Datos de Frutas
    </button>

    <button onclick={() => { data = datasetB; palette = paletteVehiculos; }}>
      Datos de Vehículos
    </button>

    <button onclick={() => legendPosition = 'right'}>
      Leyenda Derecha
    </button>

    <button onclick={() => legendPosition = 'left'}>
      Leyenda Izquierda
    </button>

    <button onclick={() => legendPosition = 'bottom'}>
      Leyenda Abajo
    </button>
  </div>

  <div id="anychart-pie-container" class="chart"></div>
</section>
<style>
  .page {
    display: grid;
    gap: 0.75rem;
    max-width: 720px;
  }
  .controls {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
  }
  .chart {
    width: 100%;
    height: 420px;
  }
  button {
    padding: 0.5rem 1rem;
    cursor: pointer;
  }
</style>

En este caso, se puede apreciar:

  • Que en el fichero «index.html» se cargan las librerías que se requieran, de forma similar a las aplicaciones WEB.
  • Que en el fichero «App.svelte» es muy similar al de la versión NPM, funcionando la reactividad de la misma forma.

Espero que os sea de utilidad esta guía y como siempre, os dejo los fuentes para que hagáis cuantos ajustes y pruebas necesitéis.

Adjuntos

Archivo Tamaño de archivo Descargas
zip anychart-svelte 20 KB 6
zip anychart2-svelte 16 KB 7

Blog personal para facilitar soporte gratuito a usuarios de React y PHPRunner