Histórico da Página
CONTEÚDO
- Cadastrando um novo Monitor exclusivo
- Criando uma nova Api de negócio
- Implementando procedure de modo Gráfico
- Implementando procedure de modo Detalhe
01. CADASTRANDO UM NOVO MONITOR EXCLUSIVO
O primeiro passo para criar o seu Monitor Exclusivo é acessar no menu do ERP Datasul o programa Gestão à Vista - Monitores Exclusivos (ou pelo código html.supply.Monitor.Custom).
Nesta rotina é possível visualizar os monitores Exclusivos já cadastrados e realizar o cadastro de um novo monitor:
Ao clicar em Adicionar, será apresentado um formulário contendo os campos abaixo:
Para o exemplo deste guia, iremos criar um monitor exclusivo para mostrar a quantidade de Ordens de Produção pela sua situação em um determinado período.
Seguindo o cadastro do Monitor, ao informar os campos conforme acima e clicar em Salvar, será habilitado a possibilidade de cadastrar Filtros para o Monitor. Os filtros são importantes pois irão garantir a possibilidade da parametrização individual deste monitor quando ele for ser adicionado em uma Visão por um usuário.
Informações | ||
---|---|---|
| ||
Pode-se notar que indicamos como nossa Api de Negócio o programa cpp/exclusivo/statusOrdem.p. Esse programa ainda não existe (iremos construí-lo em breve), porém precisamos informar seu futuro caminho no momento da criação do monitor. Se porventura o nome da API for alterado ou ela seja movida para um novo diretório, não se esqueça de atualizar esse cadastro com o novo caminho/nome. |
No nosso exemplo, iremos cadastrar apenas dois filtros, Estabelecimento e Desde quando (que irá representar o número de dias no passado que iremos buscar as ordens de produção de acordo com a sua data de criação).
Nota | ||
---|---|---|
| ||
Note que os filtros possuem como código de propriedade os valores cod-estabel e qtd-dias-atras respectivamente, isso será importante para o momento em que criaremos a Api de negócio. |
02. CRIANDO UMA NOVA API DE NEGÓCIO
Para criar a Api de negócio o primeiro passo é realizar a importação de classes do Progress que permitem a utilização de objetos do tipo JSON, além disso também é importante definir as includes disponibilizadas pelo Gestão à Vista.
Além da importação dos objetos Progress e definição de includes, também sugerimos criar uma função para verificar se existem RowErrors, essa função será útil para reaproveitamento de código posteriormente.
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
BLOCK-LEVEL ON ERROR UNDO, THROW.
USING PROGRESS.json.*.
USING PROGRESS.json.ObjectModel.*.
USING cdp.services.gestaoavista.*. //A classe ChartBuilder está definida aqui.
{method/dbotterr.i}
{cdp/services/gestaoavista/builder-utils.i}
{cdp/services/gestaoavista/monitor-utils.i}
FUNCTION fn-has-row-errors RETURNS LOGICAL ():
FOR EACH RowErrors
WHERE UPPER(RowErrors.ErrorType) = 'INTERNAL':U:
DELETE RowErrors.
END.
RETURN CAN-FIND(FIRST RowErrors
WHERE UPPER(RowErrors.ErrorSubType) = 'ERROR':U).
END FUNCTION. |
Após essa etapa, será necessário definir as procedures de acordo com a forma que o seu monitor exclusivo foi cadastrado, por exemplo:
Monitores do tipo gráfico, devem implementar a procedure pi-get-monitor-data-chart.
Monitores do tipo texto, devem implementar a procedure pi-get-monitor-data-info.
Ambos os tipos de monitores suportam o modo de detalhe das informações através da procedure pi-get-monitor-detail.
Informações | ||
---|---|---|
| ||
Neste guia vamos considerar dar continuidade com a construção de um monitor do tipo Gráfico com modo Detalhe. |
02.a. IMPLEMENTANDO A PROCEDURE DE MODO GRÁFICO
No bloco de código abaixo, iremos criar a procedure pi-get-monitor-data-chart (maiores detalhes sobre ela podem ser consultados aqui), para o nosso exemplo neste guia, iremos realizar uma query dinâmica na tabela ord-prod para realizar a contagem de Ordens x Situação aplicando os filtros de estabelecimento e desde quando que é calculado em número de dias retroativos a hoje.
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
PROCEDURE pi-get-monitor-data-chart:
DEFINE INPUT PARAMETER TABLE FOR ttVisaoMonitor.
DEFINE OUTPUT PARAMETER monitorJsonOutput AS JsonObject.
DEFINE OUTPUT PARAMETER TABLE FOR RowErrors.
DEFINE VARIABLE cFiltroEstab AS CHARACTER NO-UNDO.
DEFINE VARIABLE dFiltroData AS DATE NO-UNDO.
DEFINE VARIABLE iStatusAberto AS INTEGER NO-UNDO.
DEFINE VARIABLE iStatusAndamento AS INTEGER NO-UNDO.
DEFINE VARIABLE iStatusEncerrado AS INTEGER NO-UNDO.
DEFINE VARIABLE ChartBuilder AS ChartBuilder NO-UNDO. // Classe utilitária que ajudará na montagem do gráfico
FIND FIRST ttVisaoMonitor.
EMPTY TEMP-TABLE RowErrors.
fn-validate-properties(). // Método interno que valida automaticamente se todos os filtros marcados como obrigatórios foram preenchidos.
IF fn-has-row-errors() THEN RETURN "NOK":U.
/* Instanciar a classe passando como parâmetro a ttVisaoMonitor, que contém as informações do monitor e visão
que estão sendo processados nesse instante */
ChartBuilder = NEW ChartBuilder(INPUT TABLE ttVisaoMonitor).
/**
Pega os valores que o usuário digitou nos filtros do monitor. O valor é sempre gravado como STRING,
portanto deve-se fazer a conversão dos dados caso exista necessidade.
Importante: Os valores passados como parâmetro devem ser os mesmos informados no campo "Propriedade" no cadastro de cada filtro
**/
ASSIGN cFiltroEstab = fn-get-valor-propriedade(INPUT "cod-estabel")
dFiltroData = TODAY - INTEGER(fn-get-valor-propriedade(INPUT "qtd-dias-atras")).
FOR EACH ord-prod NO-LOCK
WHERE ord-prod.cod-estabel >= cFiltroEstab
AND ord-prod.dt-emissao >= dFiltroData:
IF ord-prod.estado < 3 THEN
ASSIGN iStatusAberto = iStatusAberto + 1.
ELSE IF ord-prod.estado <= 6 THEN
ASSIGN iStatusAndamento = iStatusAndamento + 1.
ELSE
ASSIGN iStatusEncerrado = iStatusEncerrado + 1.
END.
CREATE ttSeries.
ASSIGN ttSeries.titulo = "Pendentes"
ttSeries.valor = STRING(iStatusAberto)
ttSeries.cor = "#A0B9BF".
CREATE ttSeries.
ASSIGN ttSeries.titulo = "Em Andamento"
ttSeries.valor = STRING(iStatusAndamento)
ttSeries.cor = "#007acc".
CREATE ttSeries.
ASSIGN ttSeries.titulo = "Concluídas"
ttSeries.valor = STRING(iStatusEncerrado)
ttSeries.cor = "#26BA41".
CREATE ttTags.
ASSIGN ttTags.valor = "Tag de Exemplo"
ttTags.icone = "po-icon-calendar"
ttTags.cor-texto = "#f5f5f5"
ttTags.cor-tag = "#080707".
CREATE ttTags.
ASSIGN ttTags.valor = "Tag de Exemplo 2"
ttTags.icone = "po-icon-manufacture"
ttTags.cor-texto = "#f5f5f5"
ttTags.cor-tag = "#080707".
/* Depois que todas as entidades estão criadas, basta setá-las no objeto ChartBuilder */
ChartBuilder:setTags(INPUT TABLE ttTags).
ChartBuilder:setSeries(INPUT TABLE ttSeries).
/* Chama o método para criar e devolver o gráfico completo e guarda o resultado na variável monitorJsonOutput */
monitorJsonOutput = ChartBuilder:createChart().
DELETE OBJECT ChartBuilder.
/* Exibe o resultado no Log do AppServer, se estiver ativo */
RUN displayJsonObject(monitorJsonOutput).
CATCH eSysError AS Progress.Lang.Error:
CREATE RowErrors.
ASSIGN RowErrors.ErrorNumber = 17006
RowErrors.ErrorDescription = eSysError:getMessage(1)
RowErrors.ErrorSubType = "ERROR".
END.
FINALLY:
IF fn-has-row-errors() THEN DO:
UNDO, RETURN 'NOK':U.
END.
END FINALLY.
END. |
Após a inclusão dessa procedure na api, já é possível adicionar esse monitor exclusivo em uma Visão e observar o resultado espero conforme abaixo:
Exemplos de outros tipos de gráficos podem ser consultados em: Exemplos adicionais de monitores.
02.b. IMPLEMENTANDO A PROCEDURE DE MODO DETALHE
No bloco de código abaixo, iremos criar a procedure pi-get-monitor-data-detail (maiores detalhes sobre ela podem ser consultados aqui), através da definição dessa procedure é possível fazer o retorno do schema e dados da modal de detalhe para detalhar as informações que estão sendo mostradas no monitor (por exemplo, lista de registros que foram considerados para montar o gráfico do monitor).
No exemplo de código abaixo, ao clicar em uma parte do gráfico, será aberto uma modal com a listagem de Ordens de Produção com a situação escolhida, ou ao acionar a opção Detalhar nas configurações do monitor, será listada todas as ordens de produção que compõe o gráfico.
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
PROCEDURE pi-get-monitor-data-detail: /* O nome da procedure de detalhe sempre precisará ser esse. */
DEFINE INPUT PARAM TABLE FOR ttVisaoMonitor.
DEFINE INPUT PARAM iPage AS INTEGER NO-UNDO.
DEFINE INPUT PARAM cSerie AS CHARACTER NO-UNDO.
DEFINE INPUT PARAM cCategory AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAM detailJsonOutput AS JsonObject NO-UNDO.
DEFINE OUTPUT PARAM TABLE FOR RowErrors.
DEFINE VARIABLE DetailBuilder AS DetailBuilder NO-UNDO.
DEFINE VARIABLE lHasNext AS LOGICAL INITIAL FALSE NO-UNDO.
DetailBuilder = NEW DetailBuilder(). // Classe utilitária que ajudará na montagem do objeto de detalhe
FIND FIRST ttVisaoMonitor.
/* Procedure local que cria a temp-table das colunas que serão apresentadas na janela de detalhe */
RUN pi-get-colunas-detalhe(OUTPUT TABLE ttColunaDetalhe).
/* Opcionalmente, também podemos montar cabeçalhos (headers) para a janela de detalhe */
RUN pi-get-headers-detalhe(INPUT cSerie,
OUTPUT TABLE ttHeadersDetalhe).
/**
Executar o método que irá retornar a temp-table contendo os dados a serem apresentados no detalhe. Dessa vez não serão os totais,
e sim os registros individuais. Pode ser aproveitado o mesmo método de query que gera o gráfico do monitor, porém ele precisará
ser ajustado para levar em consideração qual fatia/coluna do gráfico o usuário clicou (variáveis cSerie e cCategory) e também precisará gerar
a tabela com os registros individuais, e não somente calcular os totais
**/
RUN pi-get-itens-detalhe(INPUT iPage,
INPUT cSerie,
OUTPUT lHasNext,
OUTPUT TABLE ttOrdemProducao).
CREATE ttTags.
ASSIGN ttTags.valor = "Tag de Exemplo"
ttTags.icone = "po-icon-calendar"
ttTags.cor-texto = "#f5f5f5"
ttTags.cor-tag = "#080707".
CREATE ttTags.
ASSIGN ttTags.valor = "Tag de Exemplo 2"
ttTags.icone = "po-icon-manufacture"
ttTags.cor-texto = "#f5f5f5"
ttTags.cor-tag = "#080707".
// Setamos as colunas, tags, headers e itens que serão exibidos
DetailBuilder:setTags(INPUT TABLE ttTags).
DetailBuilder:setColumns(INPUT TABLE ttColunaDetalhe).
DetailBuilder:setHeaders(INPUT TABLE ttHeadersDetalhe).
DetailBuilder:setItems(JsonAPIUtils:convertTempTableToJsonArray(TEMP-TABLE ttOrdemProducao:HANDLE, FALSE)). //O método convertTempTableToJsonArray transforma nossa temp-table em um Array, contendo os campos conforme os SERIALIZE-NAME definidos
/**
Se a consulta for paginada e tiver mais resultados além dos que estão sendo retornados, podemos setar a variável 'hasNext' como TRUE. Desse modo o botão
'Carregar mais resultados' ficará habilitado na janela de detalhe para que o usuário possa consultar os registros da próxima página. Recomendamos utilizar paginação
caso exista a possibilidade da consulta geral demorar mais que um minuto
**/
DetailBuilder:setHasNext(lHasNext).
DetailBuilder:setCanExportXLS(TRUE). // Determina se o botão de exportação para planilha ficará habilitado (TRUE) ou não (FALSE)
DetailBuilder:setModalMaxWidth("1440px"). // Tamanho máximo que a janela terá em tela
ASSIGN detailJsonOutput = DetailBuilder:createDetail(). //Gera o objeto de detalhe
/**
A temp-table RowErrors pode ser utilizada para retornar mensagens de erro, caso necessário:
CREATE RowErrors.
ASSIGN RowErrors.ErrorNumber = 17006
RowErrors.ErrorDescription = "ERRO DE EXEMPLO"
RowErrors.ErrorSubType = "ERROR".
**/
END PROCEDURE.
PROCEDURE pi-get-headers-detalhe:
DEFINE INPUT PARAM cSerie AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER TABLE FOR ttHeadersDetalhe.
CREATE ttHeadersDetalhe.
ASSIGN ttHeadersDetalhe.texto-header = "Listando Ordens com Situação:"
ttHeadersDetalhe.classe-header = "po-sm-12 po-font-subtitle blue-text" //Classes de tipografia do PO-UI estão disponíveis em: https://po-ui.io/guides/typography
ttHeadersDetalhe.estilo-header = "".
CREATE ttHeadersDetalhe.
ASSIGN ttHeadersDetalhe.texto-header = IF cSerie <> "" THEN cSerie ELSE "Todos"
ttHeadersDetalhe.classe-header = "po-sm-12 po-font-text-large-bold" //Classes de tipografia do PO-UI estão disponíveis em: https://po-ui.io/guides/typography
ttHeadersDetalhe.estilo-header = "".
END PROCEDURE.
PROCEDURE pi-get-colunas-detalhe:
DEFINE OUTPUT PARAMETER TABLE FOR ttColunaDetalhe.
/* Atenção! O campo 'propriedade' da ttColunaDetalhe deve conter o mesmo nome que consta no SERIALIZE-NAME do campo que será apresentado,
conforme definição da temp-table. Estamos utilizando a temp-table ttOrdemProducao neste exemplo, veja que estamos usando os nomes em inglês que
foram definidos para cada propriedade: */
CREATE ttColunaDetalhe.
ASSIGN ttColunaDetalhe.cod-label = "Ordem"
ttColunaDetalhe.propriedade = "productionOrderNumber" //productionOrderNumber equivale ao campo nr-ord-produ na temp-table ttOrdemProducao
ttColunaDetalhe.formato = "1.0-0"
ttColunaDetalhe.tipo = "number"
ttColunaDetalhe.largura = "180px".
CREATE ttColunaDetalhe.
ASSIGN ttColunaDetalhe.cod-label = "Item"
ttColunaDetalhe.propriedade = "itemCode". //itemCode equivale ao campo it-codigo na temp-table ttOrdemProducao
CREATE ttColunaDetalhe.
ASSIGN ttColunaDetalhe.cod-label = "Data de Emissão"
ttColunaDetalhe.propriedade = "creationDate" //creationDate equivale ao campo dt-emissao na temp-table ttOrdemProducao
ttColunaDetalhe.tipo = "date".
CREATE ttColunaDetalhe.
ASSIGN ttColunaDetalhe.cod-label = "Quantidade"
ttColunaDetalhe.propriedade = "quantity" //quantity equivale ao campo qt-ordem na temp-table ttOrdemProducao
ttColunaDetalhe.tipo = "number" |
Realiza a busca dos dados que serão visualizados nos monitores de Gráfico, como os categorias, séries e valores para os gráficos.
Obrigatória: Somente para monitores que possuem opção do tipo Gráfico.
Parâmetros
...
O objeto monitorJsonOutput (JsonObject) será construído através da classe utilitária ChartBuilder, que utiliza as seguintes Temp-Tables:
ttCategorias
Define os nomes das categorias que serão plotadas no eixo X do gráfico caso seja do tipo colunas, ou então nos eixos Y do grid de gráficos dos tipos barras e linhas. Para maiores informações, consulte a documentação da propriedade categories do componente po-chart (https://po-ui.io/documentation/po-chart).
...
Campo
...
Descrição
...
*Campos obrigatórios
Bloco de código | ||
---|---|---|
| ||
ChartBuilder:setCategories(INPUT TABLE ttCategorias). |
ttSeries
São os objetos de série do gráfico, que representam as colunas/fatias/linhas, conforme o tipo de gráfico. Possui as mesmas propriedades do objeto Series do PO-CHART.
...
Campo
...
Descrição
...
*Campos obrigatórios
Bloco de código | ||
---|---|---|
| ||
ChartBuilder:setSeries(INPUT TABLE ttSeries). |
ttTags (Opcional)
Temp-table com as definições das Tags que serão exibidas abaixo do título do monitor.
...
Campo
...
Descrição
...
*Campos obrigatórios
Bloco de código | ||
---|---|---|
| ||
ChartBuilder:setTags(INPUT TABLE ttTags). |
Altura (Opcional)
Altura que o gráfico terá dentro do widget. Para atribuir a altura, utilize o método setHeight() do ChartBuilder.
Bloco de código | ||
---|---|---|
| ||
ChartBuilder:setHeight(290). |
Exemplo de código
Depois de criar as temp-tables necessárias na aplicação, utilizamos a classe ChartBuilder para gerar o gráfico, conforme abaixo:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
BLOCK-LEVEL ON ERROR UNDO, THROW. USING PROGRESS.json.*. USING PROGRESS.json.ObjectModel.*. USING cdp.services.gestaoavista.*. //A classe ChartBuilder está definida aqui. {method/dbotterr.i} {cdp/services/gestaoavista/builder-utils.i} {cdp/services/gestaoavista/monitor-utils.i} FUNCTION fn-has-row-errors RETURNS LOGICAL (): FOR EACH RowErrors WHERE UPPER(RowErrors.ErrorType) = 'INTERNAL':U: DELETE RowErrors. END. RETURN CAN-FIND(FIRST RowErrors WHERE UPPER(RowErrors.ErrorSubType) = 'ERROR':U). END FUNCTION. PROCEDURE pi-get-monitor-data-chart: DEFINE INPUT PARAMETER TABLE FOR ttVisaoMonitor. DEFINE OUTPUT PARAMETER monitorJsonOutput AS JsonObject. DEFINE OUTPUT PARAMETER TABLE FOR RowErrors. DEFINE VARIABLE ChartBuilder AS ChartBuilder NO-UNDO. /* Instanciar a classe passando como parâmetro a ttVisaoMonitor, que contém as informações do monitor e visão que estão sendo processados nesse instante. */ ChartBuilder = NEW ChartBuilder(INPUT TABLE ttVisaoMonitor). /* Depois que todas as entidades estão criadas, basta setá-las no objeto ChartBuilder */ ChartBuilder:setTags(INPUT TABLE ttTags). ChartBuilder:setCategories(INPUT TABLE ttCategorias). ChartBuilder:setSeries(INPUT TABLE ttSeries). ChartBuilder:setHeight(290). /* Chama o método para criar e devolver o gráfico completo e guarda o resultado na variável monitorJsonOutput */ monitorJsonOutput = ChartBuilder:createChart(). DELETE OBJECT ChartBuilder. CATCH eSysError AS Progress.Lang.Error: CREATE RowErrors. ASSIGN RowErrors.ErrorNumber = 17006 RowErrors.ErrorDescription = eSysError:getMessage(1) ttColunaDetalhe.formato RowErrors.ErrorSubType = "ERROR". END1.4-4". FINALLY: IF fn-has-row-errors() THEN DO: END PROCEDURE. PROCEDURE pi-get-itens-detalhe: DEFINE INPUT UNDO, RETURN 'NOK':U. PARAM iPage AS END. INTEGER END FINALLY. END PROCEDURE. |
O retorno da API de negócio deverá respeitar a estrutura conforme exemplo abaixo:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "altura": 290, "categorias": [NO-UNDO. DEFINE INPUT PARAM cSerie AS CHARACTER NO-UNDO. DEFINE OUTPUT "Estados dos CTs" PARAM lHasNext AS LOGICAL ],NO-UNDO. "series": [ DEFINE OUTPUT PARAMETER TABLE FOR ttOrdemProducao. { DEFINE VARIABLE cQuery AS CHARACTER "color": "#F50031", NO-UNDO. DEFINE VARIABLE iCount AS INTEGER "data": 1.0, NO-UNDO. "label": "Parado", DEFINE VARIABLE cFiltroEstab AS CHARACTER "tooltip": "",NO-UNDO. DEFINE VARIABLE dFiltroData "type": "pie" AS DATE },NO-UNDO. { ASSIGN cFiltroEstab = fn-get-valor-propriedade(INPUT "cod-estabel") "color": "#26BA41", dFiltroData = TODAY "data": 2.0,- INTEGER(fn-get-valor-propriedade(INPUT "qtd-dias-atras")). "label": "Produzindo", ASSIGN cQuery = 'FOR EACH "tooltip": "",ord-prod NO-LOCK':U. ASSIGN cQuery = cQuery + ' WHERE "type": "pie" ord-prod.cod-estabel = "' + cFiltroEstab + '"'. ASSIGN }, cQuery = cQuery + ' AND ord-prod.dt-emissao >= { ' + STRING(dFiltroData) + ''. "color": "#007acc",/* Caso a cSerie "data": 1, "label": "Setup",esteja preenchido, irá significar que a tela de detalhes foi acionada através de uma interação com o gráfico e "tooltip": "", "type": "pie"deverá ser filtrado os dados de acordo com a série clicada */ IF cSerie <> }, "" THEN DO: IF { cSerie = "Em Aberto" THEN "color": "#A0B9BF", ASSIGN cQuery = cQuery + ' AND "data":ord-prod.estado < 3'.0, ELSE IF cSerie = "label": "Ocioso", Em Andamento" THEN "tooltip": "", ASSIGN cQuery = cQuery + ' AND "type": "pie" ord-prod.estado >= 3 AND ord-prod.estado <= 6'. } ], ELSE IF cSerie = "tagsConcluídas": THEN [ { ASSIGN cQuery = cQuery + ' "colorTexto": "", AND ord-prod.estado >= 7'. END. "icone": "po-icon-pin", DEFINE QUERY findQuery FOR ord-prod SCROLLING. QUERY "texto": "Área: 002"findQuery:QUERY-PREPARE(cQuery). },QUERY findQuery:QUERY-OPEN(). QUERY findQuery:REPOSITION-TO-ROW(iPage). { REPEAT: GET "colorTexto": "",NEXT findQuery. IF QUERY "icone": "po-icon-manufacture",findQuery:QUERY-OFF-END THEN LEAVE. IF iCount >= 50 "texto": "Produzido: 8,0000"THEN DO: }, ASSIGN lHasNext = {TRUE. "colorTexto": "",LEAVE. END. "icone": "po-icon-document-filled", CREATE ttOrdemProducao. "texto": "Previsto: 220,0000"TEMP-TABLE ttOrdemProducao:HANDLE:DEFAULT-BUFFER-HANDLE:BUFFER-COPY( }, BUFFER ord-prod:HANDLE { ). FOR FIRST item FIELDS(desc-item) WHERE item.it-codigo = "colorTexto": "", "icone": "po-icon-minus-circle", ttOrdemProducao.it-codigo NO-LOCK: END. ASSIGN ttOrdemProducao.desc-item = IF AVAIL item THEN item.desc-item ELSE ''. "texto": "Refugado: 2,0000" ASSIGN iCount = iCount + 1. }END. ] } |
O resultado em tela do retorno exemplificado acima será um monitor conforme abaixo:
END PROCEDURE. |
Com o código exemplificado acima teremos o resultado abaixo ao clicar no gráfico:
Em anexo também estamos disponibilizando o código completo utilizado neste passo a passo:
View file | ||||
---|---|---|---|---|
|
Para mais exemplos, consulte a página Exemplos adicionais de monitores.