Gráficas dinámicas en Angular con ng2-charts (Angular, Node.js, MySQL y Bootstrap).

En este proyecto utilizamos ng2-chartjs para generar gráficas en Angular, nos apoyamos de Bootstrap para la maquetación, utilizamos MySQL como base de datos, y como backend Node.Js con Express. Tantos los datos de las graficas como sus colores van a ser parametrizables. El resultado que se obtendrá de este proyecto es el siguiente.

Formulario para añadir datos y asignar el color
Datos graficados con un diagrama de barras utilizando ng2-chartjs

Requerimientos

  • Conocimientos básicos de SQL
  • Conocimientos básicos de Node.js
  • Conocimientos intermedios de Angular
  • Conocimientos básicos de Bootstrap
  • Servidor Mysql

Configuración de la Base de Datos

Creamos una base de datos con el nombre  graficas.

A continuación dejo el script de la base de datos, únicamente es una tabla, para ahorrar tiempo.

CREATE TABLE `graf_categoria` (
  `PK_CATE` int(11) NOT NULL,
  `NOMBRE_CATE` varchar(255) NOT NULL,
  `COLOR_CATE` varchar(255) NOT NULL,
  `DATOS_CATE` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `graf_categoria` (`PK_CATE`, `NOMBRE_CATE`, `COLOR_CATE`, `DATOS_CATE`) VALUES
(1, 'Hombres', '#1449a9', '1,2,3,4,5,4,10'),
(3, 'Mujeres', '#8221e2', '1,4,5,6,8'),
(4, 'Aliens', '#21d585', '1,2,3,4,5');

ALTER TABLE `graf_categoria`
  ADD PRIMARY KEY (`PK_CATE`);
  
ALTER TABLE `graf_categoria`
  MODIFY `PK_CATE` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=13;
COMMIT;

Instalación del Backend

Para no alargar tanto este proyecto ya que lo que nos interesa es ver la parte Angular, vamos a partir primero instalando lo necesario, clonamos el backend que esta en Node.js, es un proyecto muy básico para efectos de pruebas.

git clone https://github.com/VManuelPM/GraficasAngularBack

Entramos a la carpeta del proyecto GraficasAngularBack e instalamos las dependencias.

npm install

Iniciamos el proyecto back con el comando nodemon

Creación del proyecto Angular

ng new GraficasAngular
add routes = yes
styles = scss

Instalación de dependencias

Las siguientes dependencias son las necesarias para realizar gráficas en Angular

npm install --save ng2-charts
npm install --save chart.js
npm install chartjs-plugin-annotation --save

Para utilizar el color picker el cual nos permite seleccionar colores de una paleta

npm install ngx-color-picker --save
Resultado de instalar la dependencia ngx-color-picker

Importación módulos a app.module.ts

import { HttpClientModule } from '@angular/common/http';
import { ReactiveFormsModule } from '@angular/forms';
import { ChartsModule } from 'ng2-charts';
import { ColorPickerModule } from 'ngx-color-picker';

 imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    ReactiveFormsModule,
    ColorPickerModule,
    ChartsModule
  ]

Configuración rápida de Bootstrap

En el archivo index.htlm en la parte de <head></head> colocamos el cnd de Bootstrap

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">

Creación de componentes menú, grafica y categoría

Creamos tres componentes uno para el menú de la aplicación, categoría es donde se listan las categorías y se crea el formulario para agregarlas y editarlas, y finalmente grafica donde vamos a pintar nuestro diagrama.

ng g c components/categoria
ng g c components/graficas
ng g c components/menu

Configuración de rutas

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CategoriaComponent } from './components/categoria/categoria.component';
import { GraficasComponent } from './components/graficas/graficas.component';


const routes: Routes = [
  {
    path: 'categoria',
    component: CategoriaComponent
  },
  {
    path: 'categoria/:idCategoria',
    component: CategoriaComponent
  },
  {
    path: 'graficas',
    component: GraficasComponent
  },
  {
    path: '',
    component: CategoriaComponent
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Creación y configuración del Servicio

Creamos un servicio para hacer nuestras peticiones http a nuestro backend

ng g s services/categoria

Luego de la creación del servicio debemos tener un archivo llamado categoria.service.ts

Por costumbre y buenas practicas alojo la url base de mis servicios en el archivo enviroment.ts

export const environment = {
  baseUrl: 'http://localhost:3050',
  production: false
};

Luego en el categoria.service.ts creamos los servicios necesarios para nuestra aplicación

import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class CategoriaService {

  baseUrl = environment.baseUrl;
 
  constructor(protected http: HttpClient) { }

  getCategorias() {
    return this.http.get(`${this.baseUrl}/categorias`);
  }

  getCategoria(idCategoria: string){
   return this.http.get(`${this.baseUrl}/categorias/${idCategoria}`);
  }

  guardarCategoria(categoria: any) {
    return this.http.post(`${this.baseUrl}/addCategoria`, categoria);
  }

  borrarCategoria(idCategoria: number){
      return this.http.delete(`${this.baseUrl}/deleteCategoria/${idCategoria}`);
  }

  actualizarCategoria(categoria: any){
    return this.http.put(`${this.baseUrl}/updateCategoria/${categoria['idCategoria']}`, categoria);
  } 

}

Creación del menú

En el archivo menu.component.html utilizamos las clases de Bootstrap para agilizar la maquetación del front

<nav id="menu" class="navbar navbar-expand-lg navbar-light bg-light">
  <div class="collapse navbar-collapse" id="navbarSupportedContent">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item">
        <a class="nav-link" [routerLink]="['/categoria']" [routerLinkActive]="['active']">Categoria</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" [routerLink]="['/graficas']"  [routerLinkActive]="['active']">Gráficas</a>
      </li>
    </ul>
  </div>
</nav>
#menu{
    margin-bottom: 15px;
}

.active{
    border-top: 2px solid orange;
    background-color: gray;
}

Creación de la categoría

<!-- Creación del formulario -->
<div class="row">
    <div class="card col-3">
        <div class="card-body">
            <form [formGroup] = "categoriaForm" (ngSubmit)="agregarCategoria()">
                <div class="form-group">
                    <label for="categoria">Nombre de la categoria</label>
                    <input type="text" class="form-control" id="categoria" formControlName="nombreCategoria">
                </div>
                <div class="form-group">
                    <label for="categoria">Datos</label>
                    <input type="text" class="form-control" id="categoria" formControlName="datosCategoria" required>
                    <small id="emailHelp" class="form-text text-muted">Datos separados por una , ejem: 1,2,3</small>
                </div>
                <div class="form-group">
                    <label for="categoria">Color de la gráfica</label>
                    <!-- Input que permite seleccionar el color de la paleta -->
                    <input class="form-control" [(colorPicker)]="color" [style.background]="color" formControlName="colorCategoria" readonly/>
                </div>
                <div class="form-group">
                    <button class="btn btn-success" [disabled]="!categoriaForm.valid">{{textButton}}</button>
                </div>
            </form>
        </div>
    </div>

    <!-- Creación de la tabla  -->
    <div class="col-9">
        <table class="table">
            <thead class="thead-dark text-center">
                <tr>
                    <th scope="col">NOMBRE_CATE</th>
                    <th scope="col">DATOS_CATE</th>
                    <th scope="col">COLOR_CATE</th>
                    <th scope="col">OPCION</th>
                </tr>
            </thead>
            <tbody>
                <tr *ngFor="let categoria of categorias">
                    <td>{{categoria.NOMBRE_CATE}}</td>
                    <td>{{categoria.DATOS_CATE}}</td>
                    <td>
                    <input class="form-control" [(colorPicker)]="categoria.COLOR_CATE" [style.background]="categoria.COLOR_CATE" readonly disabled/>
                    </td>
                    <td>
                        <button class="btn btn-success" [routerLink]="['/categoria', categoria.PK_CATE]">Editar</button>
                        <button class="btn btn-danger" (click)="borrarCategoria(categoria.PK_CATE)">Borrar</button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
import { Component, OnInit } from '@angular/core';
import { CategoriaService } from '../../services/categoria.service';
import { FormGroup, FormBuilder } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-categoria',
  templateUrl: './categoria.component.html',
  styleUrls: ['./categoria.component.scss']
})
export class CategoriaComponent implements OnInit {

  categorias: any = [];
  color = "black";
  categoriaForm: FormGroup;
  categoria: any;
  nombreCat: string;
  datosCat: string;
  idCategoria: string;
  textButton: string;

  constructor(protected categoriaService: CategoriaService, public fb: FormBuilder, private route: ActivatedRoute) {
    this.textButton = "Agregar";
    this.obtenerParametroUrl();
    this.categoriaForm = this.fb.group({
      idCategoria: [''],
      nombreCategoria: ['', Validators.required],
      datosCategoria: ['', Validators.required],
      colorCategoria: ['']
    });
  }

  ngOnInit(): void {
    this.getCategorias();
  }

  obtenerParametroUrl() {
    this.route.paramMap.subscribe(params => {
      this.idCategoria = params.get('idCategoria');
      if (this.idCategoria) {
        this.categoriaService.getCategoria(this.idCategoria).subscribe(res => {
          this.color = res[0].COLOR_CATE;
          this.datosCat = res[0].DATOS_CATE;
          this.nombreCat = res[0].NOMBRE_CATE;
          this.categoriaForm.patchValue({
            idCategoria: [this.idCategoria],
            nombreCategoria: [this.nombreCat],
            datosCategoria: [this.datosCat],
            colorCategoria: [this.color]
          });
          this.textButton = "Actualizar";
        });
      }
    });
  }

  getCategorias() {
    this.categoriaService.getCategorias().subscribe(res => {
      this.categorias = res;
    });
  }

  agregarCategoria() {
      this.categoriaForm.controls['colorCategoria'].setValue(this.color);
      this.categoria = this.categoriaForm.value;

      if (!this.idCategoria) {
      this.categoriaService.guardarCategoria(this.categoria).subscribe(res => {
        console.log(res);
        this.getCategorias();
      });
    }
    else {
      this.categoriaService.actualizarCategoria(this.categoria).subscribe(res =>{
        console.log(res);
        this.getCategorias();
      });
    }
  }

  borrarCategoria(idCategoria) {
    this.categoriaService.borrarCategoria(idCategoria).subscribe(res => {
      console.log(res);
      this.getCategorias();
    })
  }

}

Creación de gráficos con ng2-charts

La ventaja de utilizar ng2-charts es que aprovecha al máximo las directivas, se parte de una directiva base la cual es baseChart, hay 8 tipos de gráficas line, bar, radar, pie, polarArea, doughnut, bubble and scatter. Si quieres mas información de cada gráfica la puedes encontrar en la documentación oficial.

<div>
    <div>
      <div style="display: block" *ngIf="barChartData">
        <canvas baseChart
          [datasets]="barChartData"
          [labels]="barChartLabels"
          [options]="barChartOptions"
          [plugins]="barChartPlugins"
          [legend]="barChartLegend"
          [chartType]="barChartType"
          [colors]="chartColors">
        </canvas>
      </div>
      
    </div>
  </div>
  • [datasets]=”barChartData” –> Con esta directiva se le indica los datos que vamos a graficar, barCharData es un arreglo el cual recibe dos datos
    • { data: datos, label: nombreCategoria}
  • [labels]=”barChartLabels” –> Ejes x, se utilizan para gráficas line, bar and radar
  • [chartType]=”barChartType” –> Indica el tipo de gráfica que queremos, barChartType es una variable que almacena el tipo de grafica. Ejm;
    • public barChartType: ChartType = ‘bar‘;
  • [colors]=”chartColors” –> Directiva con la cual le indicamos los colores de las graficas, charColors es un arreglo el cual recibe la siguiente estructura de datos:
    • {backgroundColorcolores}

En la parte de TypeScript tenemos el siguiente codigo:

import { Component, OnInit } from '@angular/core';
import { ChartOptions, ChartType, ChartDataSets } from 'chart.js';
import * as pluginDataLabels from 'chartjs-plugin-annotation';
import { Label } from 'ng2-charts';
import { CategoriaService } from '../../services/categoria.service';

@Component({
  selector: 'app-graficas',
  templateUrl: './graficas.component.html',
  styleUrls: ['./graficas.component.scss']
})
export class GraficasComponent implements OnInit {

  public barChartOptions: ChartOptions = {
    responsive: true,
    // We use these empty structures as placeholders for dynamic theming.
    scales: { xAxes: [{}], yAxes: [{}] },
    plugins: {
      datalabels: {
        anchor: 'end',
        align: 'end',
      }
    }
  };
  public barChartLabels: Label[] = ['2010', '2011', '2012', '2013', '2014', '2015', '2016'];
  //Tipo de grafico que queremos: ejem: line, bar, radar
  public barChartType: ChartType = 'bar';
  public barChartLegend = true;
  public barChartPlugins = [pluginDataLabels];
  //Datos que vamos a cargar en las graficas 
  public barChartData: ChartDataSets[];
  public chartColors;

  private categoria;
  private dato: string;
  //Arreglo de los datos que vamos a pasar
  private datos = [];
  //Arreglo de las categorias que vamos a pasar
  private nombreCategoria = [];
  //Arreglo de los colores que vamos a pasar
  private colores = [];

  constructor(protected categoriaService: CategoriaService) {
    this.getCategoria();
  }

  ngOnInit() {
  }

  getCategoria() {
    this.categoriaService.getCategorias().subscribe(res => {
      this.categoria = res;
      //Obtenemos las categorias y recorremos almacenando 
      //en cada arreglo lo necesario
      for (const cate of this.categoria) {
        this.dato = cate.DATOS_CATE.split(',');
        this.datos.push(this.dato);
        this.nombreCategoria.push(cate.NOMBRE_CATE);
        this.colores.push(cate.COLOR_CATE);
      }
     
      //Llamado a función
      this.cargarDatos(this.datos, this.nombreCategoria, this.colores);
    });
  }

  //Función que carga los datos en la grafica, asi como los colores
  cargarDatos(datos, nombreCategoria, colores) {
    this.barChartData = [];
    this.chartColors = [];
    
    for (const index in datos) {
      this.barChartData.push({ data: datos[index], label: nombreCategoria[index] });
      this.chartColors.push({backgroundColor: colores[index]});
    }

  }

}

Corriendo el proyecto

Para correr el proyecto lo primero que hacemos es encender nuestro servicio de MySQL que contiene nuestra base de datos, luego nos ubicamos en nuestro proyecto back y ejecutamos el comando nodemon.

En el proyecto front iniciamos el servidor de angular con ng serve, nos dirigimos a http://localhost:4200/

Repositorios del proyecto

El proyecto de Back en Node.js lo encuentras dando clic aquí.

El proyecto de Front en Angular lo encuentras dando clic aquí.

Cualquier duda déjala en los comentarios, con mucho gusto la responderé.

Video explicativo

3 Replies to “Gráficas dinámicas en Angular con ng2-charts (Angular, Node.js, MySQL y Bootstrap).”

  1. Hola. Para los que usan: angular 11.2.0, npm 6.14.11 & node v15.13.0 o superior, para evitar problemas de compatibilidad usar las siguientes dependencias:
    npm install chart.js@2.9.3 –save
    npm install ng2-charts@2.2.3 –save
    Saludos.

  2. Buenas tardes. Necesito tu ayuda pues el archivo graficas.component.ts me genera los siguientes errores y no he podido corregir:
    Error: node_modules/chartjs-plugin-annotation/types/label.d.ts:1:10 – error TS2305: Module ‘”../../@types/chart.js”‘ has no exported member ‘Color’.

    1 import { Color, FontSpec } from ‘chart.js’;
    ~~~~~
    node_modules/chartjs-plugin-annotation/types/label.d.ts:1:17 – error TS2305: Module ‘”../../@types/chart.js”‘ has no exported member ‘FontSpec’.

    1 import { Color, FontSpec } from ‘chart.js’;
    ~~~~~~~~
    node_modules/chartjs-plugin-annotation/types/options.d.ts:1:10 – error TS2305: Module ‘”../../@types/chart.js”‘ has no exported member ‘Color’.

    1 import { Color } from ‘chart.js’;
    ~~~~~
    node_modules/chartjs-plugin-annotation/types/index.d.ts:1:10 – error TS2305: Module ‘”../../@types/chart.js”‘ has no exported member ‘Plugin’.

    1 import { Plugin, ChartType } from ‘chart.js’;
    ~~~~~~

    1. Hola la importación de {color} realízala de esta modulo:

      import { Label, Color } from “ng2-charts”;

      Adicionalmente la ultima versión de ng2 charts esta poniendo problemas, te recomiendo utilizar estas versiones:

      npm install chart.js@2.9.3 –save
      npm install ng2-charts@2.2.3 –save

      Saludos.

Leave a Reply

Your email address will not be published.