Blazor -  Criando gráfico de barras (Canvas)


Hoje veremos como criar um gráfico de barras usando o componente Blazor Extensions Canvas.

Este pacote é um invólucro das APIs do HTML5 Canvas onde o Canvas 2D e o WebGL são suportados e onde os projetos do tipo Blazor Server Apps e o Blazor WebAssembly Apps são suportados.

NOTA : Atualmente, tem a versão do componente tem como alvo a versão v3.0.0-preview8 do Blazor.

Para saber detalhes da API deste componente acesse a página em :  https://github.com/BlazorExtensions/Canvas

Neste artigo eu vou apenas apresentar a utilização do componente em um projeto Blazor Server.

Para isso vamos instalar o pacote Blazor.Extensions.Canvas:

Install-Package Blazor.Extensions.Canvas

E no arquivo _Host.cshtml incluir a referência ao script:

<script src="_content/Blazor.Extensions.Canvas/blazor.extensions.canvas.js"></script>

A seguir inclua no arquivo _Imports.razor a declaração:

@using Blazor.Extensions.Canvas

Após isso, no componente em que você deseja colocar um elemento Canvas, adicione uma tag <BECanvas> e  Certifique-se de definir a @ref como um campo no seu componente:

<BECanvas Width="300" Height="400" @ref="_canvasReference" ></BECanvas>

Vamos ao exemplo...

Recursos usados:

Criando o projeto Blazor Server no VS Community 2019

bra o VS 2019 Community (versão mínima 16.4) e selecione a opção Create a New Project;

A seguir selecione a opção Blazor app e clique em next;

Informe o nome do projeto :  Blazor_ChartCanvas1, a localização e clique em Create;

A seguir teremos uma janela com duas opções :

  1. Blazor Server App
  2. Blazor WebAssembly App

Selecione a primeira opção - Blazor Server App. Não vamos usar autenticação e vamos habilitar o https.

Clique no botão Create para criar o projeto.

Antes de prosseguir vamos limpar o projeto excluindo os arquivos abaixo e suas referências:

Vamos também ajustar o arquivo NavMenu.razor deixando apenas a opção Home e a opção Vendas Mensais conforme o código a seguir:

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="vendas">
                <span class="oi oi-plus" aria-hidden="true"></span> Vendas Mensais
            </NavLink>
        </li>
    </ul>
</div>

Agora com o projeto criado e os ajustes feitos vamos incluir no projeto uma referência ao pacote Blazor.ExtensionsCanvas

Abra a opção Manage Nuget Packages for Solution do menu Tools e na guia Browse selecione o pacote na versão

Com a biblioteca instalada podemos prosseguir.

Configurando a aplicação

Precisamos adicionar o arquivo de interoperabilidade JavaScript necessário na seção <head> do arquivo _Host.cshtml da pasta Pages do projeto:

...
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>Blazor AdaptiveCards</title>
   <base href="~/" />
   <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
   <link href="css/site.css" rel="stylesheet" />
   <script src="_content/Blazor.Extensions.Canvas/blazor.extensions.canvas.js"></script>
</head>
...

Para facilitar o uso dos componentes fornecidos pelo Adaptive Cards for Blazor, vamos incluir uma referência a biblioteca no arquivo _Imports.razor:

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Blazor_ChartCanvas1
@using Blazor_ChartCanvas1.Shared
@using Blazor.Extensions.Canvas
@using Blazor.Extensions;
@using Blazor.Extensions.Canvas.Canvas2D;
@using Blazor_ChartCanvas1.Data

Criando o modelo e o serviço

Na pasta Data crie o arquivo Relatorio.cs onde vamos criar a classe Relatorio que representa os dados que vamos exibir no gráfico

    public class Relatorio
    {
        public string Item { get; set; }
        public int Quantidade { get; set; }
    }

Agora na mesma pasta vamos criar a classe RelatorioService onde vamos criar alguns dados para poder exibir:

    public class RelatorioService
    {
        public Task<Relatorio[]> GetRelatorio()
        {
            var intervalo = new Random();
            return Task.FromResult(Enumerable.Range(1, 5).Select(index => new Relatorio
            {
                Item = "item" + intervalo.Next(1, 100),
                Quantidade = intervalo.Next(20, 100),
            }).ToArray()); ;
        }
    }

A seguir vamos registrar este serviço no método ConfigureServices da classe Startup:

 public void ConfigureServices(IServiceCollection services)
 {
            services.AddRazorPages();
            services.AddServerSideBlazor();
           services.AddSingleton<RelatorioService>();
 }

Criando o componente Vendas.razor

Agora vamos criar o componente Vendas na pasta Pages onde vamos definir a utilização do componente e a criação do gráfico de barras:

@page "/vendas"
@inject RelatorioService relatorioService

<h3>Gráfico de Barras</h3>
<hr />
<BECanvas Width="500" Height="500" @ref="_canvasReference"></BECanvas>
<h3>Vendas</h3>

@code {
    private Canvas2DContext _context;
    protected BECanvasComponent _canvasReference;
    Relatorio[] itens;

    //define as cores
    private static readonly string[] coresUsadasNoGrafico = new[] {
        "#3090C7","#BDEDFF","#F9B7FF","#736AFF","#78C7C7","#B048B5","#4E387E"
        ,"#7FFFD4","#3EA99F","#EBF4FA","#F9B7FF","#8BB381",

    };
    protected override async Task OnInitializedAsync()
    {
        itens = await relatorioService.GetRelatorio();
    }
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        int xEspaco = 10;
        int XvalorPosicao = xEspaco;
        int valorMaximoDados = itens.Max(row => row.Quantidade);
        int calculoLargura = (Convert.ToInt32(_canvasReference.Width) - 100) / itens.Length;
        int alturaGrafico = Convert.ToInt32(_canvasReference.Height) - 40;
        this._context = await this._canvasReference.CreateCanvas2DAsync();
        int valorCor = 0;
        // Desenha os eixos
        await this._context.BeginPathAsync();
        await this._context.MoveToAsync(xEspaco, xEspaco);
        // primeiro desenha o eixo Y
        await this._context.LineToAsync(xEspaco, alturaGrafico);
        // desenha o eixo X
        await this._context.LineToAsync(Convert.ToInt32(_canvasReference.Width) - 10, alturaGrafico);
        await this._context.StrokeAsync();

        @foreach (var itemsArry in itens)
        {
            // Desenha as linhas e texto no eixo oX
            XvalorPosicao = XvalorPosicao + calculoLargura;
            await this._context.MoveToAsync(XvalorPosicao, alturaGrafico);
            await this._context.LineToAsync(XvalorPosicao, alturaGrafico + 15);
            await this._context.StrokeAsync();
            await this._context.SetFillStyleAsync("#034560");
            await this._context.SetFontAsync("14pt Calibri");
            await this._context.StrokeTextAsync(itemsArry.Item, XvalorPosicao - 40, alturaGrafico + 24);

            // Desenha o gráfico de barras
            var barrasRaio = valorMaximoDados - itemsArry.Quantidade;

            // Desenha a altura das barras
            var barrasPreenchimentoAltura = Convert.ToInt32(barrasRaio * (Convert.ToInt32(alturaGrafico - xEspaco)));
            barrasPreenchimentoAltura = Convert.ToInt32(alturaGrafico) - barrasRaio;

            // Define o preenchimento da cor de fundo
            await this._context.SetFillStyleAsync("#000000");
            await this._context.FillRectAsync(XvalorPosicao - calculoLargura - 1, alturaGrafico - 1, calculoLargura + 3, -(barrasPreenchimentoAltura - 58));

            // barras atuais
            await this._context.SetFillStyleAsync(coresUsadasNoGrafico[valorCor]);
            await this._context.FillRectAsync(XvalorPosicao - calculoLargura, alturaGrafico, calculoLargura + 2, -(barrasPreenchimentoAltura - 60));

            // Incluir o valor em cada barra
            await this._context.SetFillStyleAsync("#000000");
            await this._context.SetFontAsync("20pt Calibri");
            await this._context.FillTextAsync(itemsArry.Quantidade.ToString(), XvalorPosicao - calculoLargura + 4, alturaGrafico / 2);
            await this._context.SetFillStyleAsync(coresUsadasNoGrafico[valorCor]);

            //finaliza o gráfico
            valorCor = valorCor + 1;
        }
    }
}

No código injetamos o serviço criado usando a diretiva @inject e usamos o componente aplicando a tag :
<BECanvas Width="500" Height="500" @ref="_canvasReference"></BECanvas>

A seguir definimos as cores usadas e no método OnInitializedAsync obtemos os dados usando o serviço criado. Depois no método  OnAfterRenderAsync definimos a renderização do gráfico.

Obs: Para detalhes da criação do gráfico consulte a API do componente.

Agora é só alegria...

Executando o projeto teremos o resultado abaixo:

Esse é mais um componente para criar gráficos no Blazor. Talvez não seja o melhor ou o mais indicado, isso você decide.

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

"Bendito seja o Deus e Pai de nosso Senhor Jesus Cristo, o Pai das misericórdias e o Deus de toda a consolação;
Que nos consola em toda a nossa tribulação, para que também possamos consolar os que estiverem em alguma tribulação, com a consolação com que nós mesmos somos consolados por Deus."
2 Coríntios 1:3,4

Referências:


José Carlos Macoratti