Blazor - Despesas Pessoais com Gráfico de Barras e Pizza - IV


Neste artigo vamos iniciar a criação de uma aplicação Blazor Web Assemply para gerenciar despesas pessoais onde vamos criar gráficos de barras e de pizza.

Continuando a terceira parte do artigo vamos criar o dashboard e gerar o gráfico de barras e de pizza.

Usando a biblioteca Radzen

Para gerar os gráficos neste projeto vamos usar a biblioteca Blazor Radzen.

A biblioteca Blazor Radzen, que pode ser instalada como um pacote Nuget,  apresenta dezenas de componentes gratuitos que podem ser usados em aplicações Blazor do lado do cliente (WASM) e do lado do servidor.

Esses componentes são implementados na linguagem C# e aproveitam ao máximo do framework Blazor não sendo dependentes de frameworks ou bibliotecas JavaScripts já existentes. A inclusão de novos componentes e recursos é feita regularmente, segundo o site da Radzen.

Nota: Existe uma versão paga como parte da assinatura do Radzen Professional.

A seguir alguns recursos que ajudam a dar mais produtividade no desenvolvimento Blazor:

Instalação

Os componentes Radzen Blazor são distribuídos como um pacote nuget Radzen.Blazor (atualmente na versão 3.3.1) :

https://www.nuget.org/packages/Radzen.Blazor/

Você pode adicionar o pacote ao seu projeto de uma das seguintes maneiras :

Ao final o pacote será incluído como um dependência em seu projeto:

Configuração e uso

A seguir um roteiro de utilização para as tarefas mais comuns em aplicações Blazor:

1. Importar o namespace

Abra o arquivo _Imports.razor da sua aplicação Blazor e inclua as duas linhas de código abaixo:
  1. @using Radzen
  2. @using Radzen.Blazor

2. Incluir um theme

Abra o arquivo wwwroot/index.html (client-side Blazor) e inclua o arquivo de tema CSS adicionando o código a seguir:

<link rel="stylesheet" href="_content/Radzen.Blazor/css/default.css">

3. Includir a referência ao Radzen.Blazor.js

Abra o  wwwroot/index.html (client-side Blazor) e inclua o arquivo javascript adicionando o código a seguir:

<script src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>


Para usar qualquer componente Radzen Blazor basta informar o nome da sua tag em uma página Blazor.

Ex:  <RadzenChart></RadzenChart>

Criando o componente Dashboard

Vamos criar um componente Dashboard.razor na pasta Pages do projeto Client. Neste componente vamos usar o componente <RadzenChart> do Blazor Radzen e vamos usar o componente <GraficoPizza> que iremos criar a seguir.

@page "/dashboard"

@inject IDataService _dataService;

<div class="row">
    <div class="col-lg-12">
        <div class="card">
            <div class="card-header">
                @anoAtual - Receitas e Despesas
            </div>
            <div class="card-body">
                <RadzenChart>
                    <RadzenColumnSeries Fill="#AACDBE" Data="@receitasAnuais" CategoryProperty="Mes" ValueProperty="Valor" Title="Receitas" LineType="LineType.Dashed" />
                    <RadzenColumnSeries Fill="#EA907A" Data="@despesasAnuais" CategoryProperty="Mes" ValueProperty="Valor" Title="Despesas" LineType="LineType.Dashed" />
                    <RadzenColumnOptions Radius="5" />
                    <RadzenValueAxis Formatter="@FormataMoeda">
                        <RadzenGridLines Visible="true" />
                        <RadzenAxisTitle Text="Valores (em R$)" />
                    </RadzenValueAxis>
                </RadzenChart>

            </div>
        </div>
    </div>
</div>

@if (receitas != null)
{
    <div class="row mt-4">
        <div class="col-lg-12">
            <div class="card">
                <div class="card-header">
                    Receitas - Trimestrais
                </div>
                <div class="card-body">
                    <div style="display: flex">
                        <GraficoPizza Colors="@colors" Data="@receitas.MesAtual" />
                        <GraficoPizza Colors="@colors" Data="@receitas.MesProximo" />
                        <GraficoPizza Colors="@colors" Data="@receitas.MesAnterior" />

                    </div>
                </div>
            </div>
        </div>
    </div>
}

@if (despesas != null)
{
    <div class="row mt-4">
        <div class="col-lg-12">
            <div class="card">
                <div class="card-header">
                    Despesas - Trimestrais
                </div>
                <div class="card-body">
                    <div style="display: flex">
                        <GraficoPizza Colors="@colors" Data="@despesas.MesAtual" />
                        <GraficoPizza Colors="@colors" Data="@despesas.MesProximo" />
                        <GraficoPizza Colors="@colors" Data="@despesas.MesAnterior" />

                    </div>
                </div>
            </div>
        </div>
    </div>
}

@code {
    private static int anoAtual = DateTime.Today.Year;
    private ICollection<string> colors = new List<string>() { "#FBC687", "#F4F7C5", "#93ABD3" };

    private ICollection<BaseAnual> despesasAnuais;
    private ICollection<BaseAnual> receitasAnuais;

    private BaseTrimestral receitas;
    private BaseTrimestral despesas;

    string FormataMoeda(object value)
    {
        return ((double)value).ToString("C0", CultureInfo.CreateSpecificCulture("pt-BR"));
    }

    protected override async Task OnInitializedAsync()
    {
        despesasAnuais = await _dataService.CarregaDespesasAnuais();
        receitasAnuais = await _dataService.CarregaReceitasAnuais();

        receitas = await _dataService.CarregaReceitasTrimestrais();
        despesas = await _dataService.CarregaDespesasTrimestrais();

        StateHasChanged();
    }
}

Neste componente vamos exibir um gráfico de barras e dois gráficos de pizza, um para receitas e outro para despesas, e, para isso vamos ter que criar o componente GraficoPizza.razor  e também criar um serviço chamado DataService onde vamos definir os dados que vamos exibir nos gráficos.

Criando o componente GraficoPizza

Na pasta Components crie o componente Razor GraficoPizza.razor com o código abaixo:

<div>

    <div style="margin-top: 15px; margin-right: 90px; text-align: center;"><b>@Data.Label</b></div>

    <RadzenChart>
        <RadzenPieSeries Fills="@Colors"
                         Data="@Data.Dados"
                         Title="@Data.Label"
                         CategoryProperty="Categoria"
                         ValueProperty="Valor" />
    </RadzenChart>

</div>
@code
{
    [Parameter]
    public ICollection<string> Colors { get; set; }

    [Parameter]
    public DadosMensais Data { get; set; }

    [Parameter]
    public string Label { get; set; }
}

Neste componente estamos criando um gráfico de Pizza usando os componentes  <RadzenChart> e <RadzenPieSeries> onde definimos 3 parâmetros : Colors , Data (DadosMensais) e Label.

Criando o serviço de dados

O serviço de dados é a parte mais importante na criação dos gráficos e iremos criar uma pasta Services no projeto e nesta pasta definir as seguintes classes:

1- ItemMensal

public class ItemMensal
{
  public decimal Valor { get; set; }
  public string Categoria { get; set; }
}

2- BaseAnual

public class BaseAnual
{
public string Mes { get; set; }
public decimal Valor { get; set; }
}

3- BaseTrimestral

public class BaseTrimestral
{
  public DadosMensais MesAtual { get; set; }
  public DadosMensais MesProximo { get; set; }
  public DadosMensais MesAnterior { get; set; }
}

4- DadosMensais

public class DadosMensais
{
     public ICollection<ItemMensal> Dados { get; set; }
     public string Label { get; set; }
}

A seguir vamos definir uma interface IDataService onde vamos definir o contrato para implementar os métodos que vão gerar os dados para serem exibidos nos gráficos:

using System.Collections.Generic;
using System.Threading.Tasks;

namespace Financas.Pessoais.Client.Services
{
    public interface IDataService
    {
        Task<ICollection<BaseAnual>> CarregaReceitasAnuais();
        Task<ICollection<BaseAnual>> CarregaDespesasAnuais();
        Task<BaseTrimestral> CarregaReceitasTrimestrais();
        Task<BaseTrimestral> CarregaDespesasTrimestrais();
    }
}

A seguir vamos implementar essa interface na classe DataService:

using Financas.Pessoais.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace Financas.Pessoais.Client.Services
{
    public class DataService : IDataService
    {
        private readonly HttpClient _httpClient;
        private readonly int _anoAtual = DateTime.Today.Year;
        public DataService(HttpClient http)
        {
            _httpClient = http;
        }
        public async Task<ICollection<BaseAnual>> CarregaReceitasAnuais()
        {
            var data = await _httpClient.GetFromJsonAsync<Receita[]>("api/Receitas");
            return data.Where(receita => receita.Data >= new DateTime(_anoAtual, 1, 1)
                && receita.Data <= new DateTime(_anoAtual, 12, 31))
                .GroupBy(receita => receita.Data.Month)
                .OrderBy(receita => receita.Key)
                .Select(receita => new BaseAnual
                {
                    Mes = GetMesTexto(receita.Key, _anoAtual),
                    Valor = receita.Sum(item => item.Valor)
                })
                .ToList();
        }
        public async Task<ICollection<BaseAnual>> CarregaDespesasAnuais()
        {
            var data = await _httpClient.GetFromJsonAsync<Despesa[]>("api/Despesas");
            return data.Where(despesa => despesa.Data >= new DateTime(_anoAtual, 1, 1)
                && despesa.Data <= new DateTime(_anoAtual, 12, 31))
                .GroupBy(despesa => despesa.Data.Month)
                .OrderBy(despesa => despesa.Key)
                .Select(despesa => new BaseAnual
                {
                    Mes = GetMesTexto(despesa.Key, _anoAtual),
                    Valor = despesa.Sum(item => item.Valor)
                })
                .ToList();
        }
        public async Task<BaseTrimestral> CarregaReceitasTrimestrais()
        {
            var mesAtual = DateTime.Today.Month;
            var mesProximo = DateTime.Today.AddMonths(-1);
            var mesAnterior = DateTime.Today.AddMonths(-2);
            return new BaseTrimestral
            {
                MesAtual = new DadosMensais
                {
                    Dados = await GetReceitasMensais(mesAtual, _anoAtual),
                    Label = GetMesTexto(mesAtual, _anoAtual)
                },
                MesProximo = new DadosMensais
                {
                    Dados = await GetReceitasMensais(mesProximo.Month, mesProximo.Year),
                    Label = GetMesTexto(mesProximo.Month, mesProximo.Year)
                },
                MesAnterior = new DadosMensais
                {
                    Dados = await GetReceitasMensais(mesAnterior.Month, mesAnterior.Year),
                    Label = GetMesTexto(mesAnterior.Month, mesAnterior.Year)
                }
            };
        }
        private async Task<ICollection<ItemMensal>> GetReceitasMensais(int mes, int ano)
        {
            var data = await _httpClient.GetFromJsonAsync<Receita[]>("api/Receitas");
            return data.Where(receita => receita.Data >= new DateTime(ano, mes, 1)
                && receita.Data <= new DateTime(ano, mes, UltimoDiaMes(mes, ano)))
                .GroupBy(receita => receita.Categoria)
                .Select(receita => new ItemMensal
                {
                    Valor = receita.Sum(item => item.Valor),
                    Categoria = receita.Key.ToString()
                })
                .ToList();
        }
        public async Task<BaseTrimestral> CarregaDespesasTrimestrais()
        {
            var mesAtual = DateTime.Today.Month;
            var mesProximo = DateTime.Today.AddMonths(-1);
            var mesAnterior = DateTime.Today.AddMonths(-2);
            return new BaseTrimestral
            {
                MesAtual = new DadosMensais
                {
                    Dados = await GetDespesasMensais(mesAtual, _anoAtual),
                    Label = GetMesTexto(mesAtual, _anoAtual)
                },
                MesProximo = new DadosMensais
                {
                    Dados = await GetDespesasMensais(mesProximo.Month, mesProximo.Year),
                    Label = GetMesTexto(mesProximo.Month, mesProximo.Year)
                },
                MesAnterior = new DadosMensais
                {
                    Dados = await GetDespesasMensais(mesAnterior.Month, mesAnterior.Year),
                    Label = GetMesTexto(mesAnterior.Month, mesAnterior.Year)
                }
            };
        }
        private async Task<ICollection<ItemMensal>> GetDespesasMensais(int mes, int ano)
        {
            var data = await _httpClient.GetFromJsonAsync<Despesa[]>("api/Despesas");
            return data.Where(despesa => despesa.Data >= new DateTime(ano, mes, 1)
                && despesa.Data <= new DateTime(ano, mes, UltimoDiaMes(mes, ano)))
                .GroupBy(despesa => despesa.Categoria)
                .Select(despesa => new ItemMensal
                {
                    Valor = despesa.Sum(item => item.Valor),
                    Categoria = despesa.Key.ToString()
                })
                .ToList();
        }
        private static int UltimoDiaMes(int mes, int ano)
        {
            return DateTime.DaysInMonth(ano, mes);
        }

        private static string GetMesTexto(int mes, int ano)
        {
            return mes switch
            {
                1 => $"Janeiro {ano}",
                2 => $"Fevereiro {ano}",
                3 => $"Março {ano}",
                4 => $"Abril {ano}",
                5 => $"Maio {ano}",
                6 => $"Junho {ano}",
                7 => $"Julho {ano}",
                8 => $"Agosto {ano}",
                9 => $"Setembro {ano}",
                10 => $"Outubro {ano}",
                11 => $"Novembro {ano}",
                12 => $"Dezembro {ano}",
                _ => throw new NotImplementedException(),
            };
        }
    }
}

Observe que a implementação do serviço acessa a respectiva API (Despesa/Receita) no projeto Server para obter os dados a partir do repositório.

Com os dados obtidos montamos a respectiva consulta para retornar :

  1. Receitas e Despesas Mensais
  2. Receitas e Despesas Anuais
  3. Receitas e Despesas Trimestrais

Para usar o serviço injetamos no componente Dashboard.razor usando a diretiva @inject IDataService

Executando o projeto teremos:

Pegue o projeto aqui :  Financas.zip (sem as referências)

"Brame o mar e a sua plenitude; o mundo, e os que nele habitam.
Os rios batam as palmas; regozijem-se também as montanhas,
Perante a face do Senhor, porque vem a julgar a terra; com justiça julgará o mundo, e o povo com eqüidade."
Salmos 98:7-9

Referências:


José Carlos Macoratti