Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introdução

O Dashboard Personalizado (Generic Dashboard) é uma ferramenta que permite montar dashboards de acordo com a necessidade dos clientes sem a intervenção do Core. Nessa ferramenta são usados os componentes HighCharts, responsável pela geração dos gráficos e o gridstack.js para a organização do Layout, possibilitando:

  1. Cria Dashboards específicos e seus respectivos gráficos;
  2. Obter dados específicos usando consultas anônimas (não foi adotada a classe EntryPoint do NFS Core);
  3. Salvar as configuração dos gráficos, permitindo alterá-los a qualquer momento;
  4. Montar Gráficos usando as configurações e os dados obtidos;
  5. Permitir alterar a disposição/tamanho dos gráficos.

Pré-requesitos

São pré-requisitos para esse módulo:

  1. Módulo "Rotas Personalizada" habilitado e rota personalizada para "/custom/genericDashboad/" criada (veja como);
  2. Tabelas "nfs_generic_dashboard" e "nfs_generic_chart" criadas na seguinte estrutura:
CREATE TABLE `nfs_generic_dashboard` (
  `SEQ_DB` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `EMPRESA` int(11) DEFAULT NULL,
  `FILIAL` int(11) DEFAULT NULL,
  `LOCAL` int(11) DEFAULT NULL,
  `TITLE` varchar(100) DEFAULT NULL,
  `FILTERS` text,
  `OPTIONS` text,
  `LAYOUT` text,
  `ENABLED` int(1) DEFAULT '1',
  UNIQUE KEY `SEQ_DB_UNIQUE` (`SEQ_DB`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

CREATE TABLE `nfs_generic_chart` (
  `SEQ_DB` bigint(20) NOT NULL AUTO_INCREMENT,
  `DASHBOARD_SEQ_DB` bigint(20) NOT NULL,
  `EMPRESA` int(11) NOT NULL,
  `FILIAL` int(11) NOT NULL,
  `LOCAL` int(11) NOT NULL,
  `TITLE` varchar(100) NOT NULL,
  `CONFIG` text NOT NULL,
  `FILTERS` text,
  `QUERIES` text,
  `INS_DH` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `ENABLED` int(11) DEFAULT '1',
  `DELETED` int(11) DEFAULT '0',
  `OPTIONS` json NULL,
  `TYPE` enum('H','E','G') DEFAULT 'H',
  `CHART_ORDER` int(11) DEFAULT 999 NULL,
  UNIQUE KEY `SEQ_DB_UNIQUE` (`SEQ_DB`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

Configurações Iniciais

Tabela nfs_generic_dashboard

Possui as definições principais do dashboard, sendo:

  1. TITLE: Título do Dashboard exibido no topo do módulo;
TITLE='Generic Dashboard'
  1. FILTERS: JSON com definições dos filtros;
{
  "inicio": {
    "type": "Date",
    "options": {
      "label": "De",
      "required": false,
      "language": "pt-BR"
    }
  },
  "fim": {
    "type": "Date",
    "options": {
      "label": "Até",
      "required": false,
      "language": "pt-BR"
    }
  },
  "equipamento": {
    "type": "Table",
    "options": {
      "label": "Equipamento",
      "required": true,
      "multiple": true
    }
  }
}
  1. OPTIONS: coluna criada para futuras personalizações.

  2. LAYOUT: coluna usada para salvar as definições de layout através do gridstack.js.

Tabela nfs_generic_chart

Tabela usada para armazenar as configurações dos gráficos dos dashboards, sendo:

  1. DASHBOARD_SEQ_DB: FK da tabela nfs_generic_dashboard;
DASHBOARD_SEQ_DB=1
  1. TITLE: Título do Gráfico;
TITLE='Generic Dashboard'
  1. CONFIG: Configuração do gráfico, variando de acordo com o tipo e opções do gráfico;
{
    "chart": {
        "type": "bar"
    },
    "title": {
        "text": "Atividades por Modelos"
    },
    "xAxis": {
        "categories": this.vals.categories
    },
    "yAxis": {
        "min": 0,
        "title": {
            "text": "Atividades por Modelos"
        }
    },
    "legend": {
        "reversed": true
    },
    "plotOptions": {
        "series": {
        "stacking": "normal"
        }
    },
    "series": this.vals.series
}
'

Os valores expressos por this.vals.categories e this.vals.series são usando como placeholders dentro das configurações do gráfico. Eles serão substituídos por dados obtidos das queries do gráfico. Todos os placeholders devem usar o préfixo "this.vals", seguido do nome da série, p.e. this.vals.<nome-da-serie>, sempre atentando para caixa baixa. As definições dos placeholders serão definidos na coluna QUERIES mais adiante. {.is-danger}

  1. FILTERS: JSON com definições dos filtros (similar ao usado em nfs_generic_dashboard, exceto que filtra apenas o gráfico ao qual pertence);
{
  "inicio": {
    "type": "Date",
    "options": {
      "label": "De",
      "required": false,
      "language": "pt-BR"
    }
  },
  "fim": {
    "type": "Date",
    "options": {
      "label": "Até",
      "required": false,
      "language": "pt-BR"
    }
  },
  "equipamento": {
    "type": "Table",
    "options": {
      "label": "Equipamento",
      "required": true,
      "multiple": true
    }
  }
}
  1. QUERIES: JSON contendo as definições dos placeholders especificados na coluna CONFIG (no exemplo, this.vals.categories e this.vals.series). Obs: Formato abaixo não é recomendado.
{
  "categories": {
    "CODE_TYPE": "PHP",
    "CONTENT": "$user = \\core\\xDS::getUser();\r\n$empresa = $user->EMPRESA_ATUAL;\r\n$local = $user->LOCAL_ATUAL;\r\n$filial = $user->FILIAL_ATUAL;\r\n$timezone = $this->user->TIMEZONE ?? date_default_timezone_get();\r\n\r\n$dIni = (empty($inicio)) ? new DateTime('-2 months', new DateTimeZone($timezone)) : DateTime::createFromFormat('d\\/m\\/Y', $inicio);\r\n$dFim = (empty($fim)) ? new DateTime('now', new DateTimeZone($timezone)) : DateTime::createFromFormat('d\\/m\\/Y', $fim);\r\n$comeco = date_format($dIni,'Y-m-d');\r\n$final = date_format($dFim,'Y-m-d');\r\n\r\nif (!empty($modelo)) {\r\n\t$modelos = (is_array($modelo)) ? ' and m.SEQ_DB in (' . implode(',', $modelo) . ') ' : \" and m.SEQ_DB = {$modelo} \";\r\n} else {\r\n\t$modelos = '';\t\r\n}\r\n\r\n$sql = \"SELECT \r\n\tdistinct m.DESCRICAO Modelo\r\n\tFROM app_apontamento_maquina a\r\n\tINNER JOIN app_equipamento e ON a.EQUIPAMENTO_SEQ_DB = e.SEQ_DB\r\n\tINNER JOIN app_modelo m ON e.MODELO_SEQ_DB = m.SEQ_DB\r\n\tWHERE DATE(a.INI_DH) between '{$comeco}' and '{$final}' \r\n\tand a.empresa = {$empresa} and a.local in ({$local},9999) and a.filial in ({$filial},9999) and a.ativo = 1 and a.deleted = 0\r\n\t{$modelos} GROUP BY m.SEQ_DB;\";\r\n\r\n$todosModelos = \\core\\ConexaoDB::query($sql, $params = []);\r\n$series = [];\r\nforeach ($todosModelos as $model){\r\n\tarray_push($series,$model['Modelo']);\r\n}\r\nreturn $series;"
  },
  "series": {
    "CODE_TYPE": "PHP",
    "CONTENT": "$user = \\core\\xDS::getUser();\r\n$empresa = $user->EMPRESA_ATUAL;\r\n$local = $user->LOCAL_ATUAL;\r\n$filial = $user->FILIAL_ATUAL;\r\n\r\n$timezone = $this->user->TIMEZONE ?? date_default_timezone_get();\r\n$dIni = (empty($inicio)) ? new DateTime('-2 months', new DateTimeZone($timezone)) : DateTime::createFromFormat('d\\/m\\/Y', $inicio);\r\n$dFim = (empty($fim)) ? new DateTime('now', new DateTimeZone($timezone)) : DateTime::createFromFormat('d\\/m\\/Y', $fim);\r\n$comeco = date_format($dIni,'Y-m-d');\r\n$final = date_format($dFim,'Y-m-d');\r\n\r\nif (!empty($modelo)) {\r\n\t$modelos = (is_array($modelo)) ? ' and m.SEQ_DB in (' . implode(',', $modelo) . ') ' : \" and m.SEQ_DB = {$modelo} \";\r\n} else {\r\n\t$modelos = '';\t\r\n}\r\n\r\n$series = [];\r\n$tipoAtividades = \"SELECT\r\n\tDISTINCT (case when (ati.FLAG_PRODUTIVA = 1) then 'Produtiva' \r\n\twhen (ati.FLAG_PRODUTIVA = 0 AND ati.FLAG_MANUTENCAO = 0 AND ati.FLAG_ABASTECIMENTO = 0 AND ga.FLAG_PARADA_PROCESSO = 0 AND ga.FLAG_PARADA_OPERACIONAL = 0) then 'Improdutiva' \r\n\twhen (ati.FLAG_PRODUTIVA = 0 AND ati.FLAG_MANUTENCAO = 1 AND ati.FLAG_ABASTECIMENTO = 0 AND ga.FLAG_PARADA_PROCESSO = 0 AND ga.FLAG_PARADA_OPERACIONAL = 0) then 'Paradas de Manutencao'\r\n\twhen (ati.FLAG_PRODUTIVA = 0 AND ati.FLAG_MANUTENCAO = 0 AND ati.FLAG_ABASTECIMENTO = 1 AND ga.FLAG_PARADA_PROCESSO = 0 AND ga.FLAG_PARADA_OPERACIONAL = 0) then 'Abastecimento'\r\n\twhen (ati.FLAG_PRODUTIVA = 0 AND ati.FLAG_MANUTENCAO = 0 AND ati.FLAG_ABASTECIMENTO = 0 AND ga.FLAG_PARADA_PROCESSO = 1 AND ga.FLAG_PARADA_OPERACIONAL = 0) then 'Paradas de Processo'\r\n\twhen (ati.FLAG_PRODUTIVA = 0 AND ati.FLAG_MANUTENCAO = 0 AND ati.FLAG_ABASTECIMENTO = 0 AND ga.FLAG_PARADA_PROCESSO = 0 AND ga.FLAG_PARADA_OPERACIONAL = 1) then 'Paradas Operacionais'\r\n\tEND) Atividades\r\n\tFROM app_apontamento_maquina a\r\n\tINNER JOIN app_equipamento e ON a.EQUIPAMENTO_SEQ_DB = e.SEQ_DB\r\n\tINNER JOIN app_modelo m ON e.MODELO_SEQ_DB = m.SEQ_DB\r\n\tINNER JOIN app_atividade ati ON a.ATIVIDADE_SEQ_DB = ati.SEQ_DB\r\n\tINNER JOIN app_grupo_atividade ga ON ati.GRUPO_ATIVIDADE_SEQ_DB = ga.SEQ_DB\r\n\tWHERE DATE(a.INI_DH) BETWEEN '{$comeco}' AND '{$final}' and a.empresa = {$empresa} and a.local in ({$local},9999) and a.filial in ({$filial},9999) and a.ativo = 1 and a.deleted = 0\r\n     {$modelos};\";\r\n\r\n$grupoAtividades = \\core\\ConexaoDB::query($tipoAtividades);\r\nforeach ($grupoAtividades as $Atividades){\r\n\tif ($Atividades['Atividades'] == 'Abastecimento') {\r\n\t\tcontinue;\r\n\t} else {\r\n\t\t$valores['name'] = [];\r\n\t\t$valores['data'] = [];\r\n\t\t$valores['name'] = $Atividades['Atividades'];\r\n\t\t$variavel = $Atividades['Atividades'];\r\n\t\t$dados = \"\r\n\t\tSELECT DISTINCT m.DESCRICAO fabricante, \r\n\t\t\tif('{$Atividades['Atividades']}' = 'Produtiva',(SUM(IF(ati.FLAG_PRODUTIVA = 1,a.INI_FIM_DIFF_SEC,0))/ SUM(IF(DATE(a.INI_DH) BETWEEN '{$comeco}' AND '{$final}', a.INI_FIM_DIFF_SEC,0)))*100,0)'Produtiva',\r\n\t\t\tif('{$Atividades['Atividades']}' = 'Improdutiva',(SUM(IF(ati.FLAG_PRODUTIVA = 0 AND ati.FLAG_MANUTENCAO = 0 AND ati.FLAG_ABASTECIMENTO = 0 AND ga.FLAG_PARADA_PROCESSO = 0 AND ga.FLAG_PARADA_OPERACIONAL = 0,a.INI_FIM_DIFF_SEC,0))/ SUM(IF(DATE(a.INI_DH) BETWEEN '{$comeco}' AND '{$final}', a.INI_FIM_DIFF_SEC,0)))*100,0) 'Improdutiva',\r\n\t\t\tif('{$Atividades['Atividades']}' = 'Paradas de Processo',(SUM(IF(ga.FLAG_PARADA_PROCESSO = 1,a.INI_FIM_DIFF_SEC,0))/ SUM(IF(DATE(a.INI_DH) BETWEEN '{$comeco}' AND '{$final}', a.INI_FIM_DIFF_SEC,0)))*100,0) 'Paradas de Processo', \r\n\t\t\tif('{$Atividades['Atividades']}' = 'Paradas Operacionais',(SUM(IF(ga.FLAG_PARADA_OPERACIONAL = 1,a.INI_FIM_DIFF_SEC,0))/ SUM(IF(DATE(a.INI_DH) BETWEEN '{$comeco}' AND '{$final}', a.INI_FIM_DIFF_SEC,0)))*100,0) 'Paradas Operacionais', \r\n\t\t\tif('{$Atividades['Atividades']}' = 'Paradas de Manutencao',(SUM(IF(ati.FLAG_MANUTENCAO = 1,a.INI_FIM_DIFF_SEC,0))/ SUM(IF(DATE(a.INI_DH) BETWEEN '{$comeco}' AND '{$final}', a.INI_FIM_DIFF_SEC,0)))*100,0) 'Paradas de Manutencao'\r\n\t\t\tFROM app_apontamento_maquina a\r\n\t\t\tINNER JOIN app_equipamento e ON a.EQUIPAMENTO_SEQ_DB = e.SEQ_DB\r\n\t\t\tINNER JOIN app_modelo m ON e.MODELO_SEQ_DB = m.SEQ_DB\r\n\t\t\tINNER JOIN app_atividade ati ON a.ATIVIDADE_SEQ_DB = ati.SEQ_DB\r\n\t\t\tINNER JOIN app_grupo_atividade ga ON ati.GRUPO_ATIVIDADE_SEQ_DB = ga.SEQ_DB\r\n\t\t\tWHERE DATE(a.INI_DH) BETWEEN '{$comeco}' AND '{$final}' and a.empresa = {$empresa} and a.local in ({$local},9999) and a.filial in ({$filial},9999) and a.ativo = 1 and a.deleted = 0\r\n{$modelos} GROUP BY m.SEQ_DB;\";\r\n\r\n\t\t$dadosSomados = \\core\\ConexaoDB::query($dados);\r\n\t\tforeach ($dadosSomados as $resultados){\r\n\t\t\tarray_push($valores['data'], floatval($resultados[$variavel]));\r\n\t\t}\r\n\t\tarray_push($series, $valores);\r\n\t}\r\n}\r\nreturn $series;"
  }
}

Onde:

  • Cada série refere-se a um placeholder na configuração;
  • CODE_TYPE: tipo de interpretação dos dados. Entre as interpretações estão;
    1. CONST: retorna apenas um valor, sendo constante (número, string, boleano) ou variável;
    2. PHP: bloco de código PHP para obtenção dos dadostrução de código.

Exemplo com CODE_TYPE = ENTRY_POINT

/*
Dados enviados pelo core:
    $entryPointsDashboards->param = $filtersConfigAndValues;
    $entryPointsDashboards->parameters = $filtersConfigAndValues;
    $entryPointsDashboards->param['inputValues'] = $filtersConfigAndValues;
    $entryPointsDashboards->processEntryPoint();

O core recebe a propriedade 'data' para enviar ao gráfico JS:

    $returnContent = $entryPointsDashboards->data;
*/


$this->queryData['apontamentosPorHora'] = $apontamentosPorHora;
$this->queryData['apontamentos'] = $apontamentosList;
$this->queryData['equipamentos'] = $equipamentos;s. É possível acessar o resultado dos filtros aplicados.
  3. JSON: Retora um JSON válido
  4. ENTRY_POINT: Executa um Entry Point DASHBOARD com nome = CHART.NAME
- **CONTENT**: o valor ou nome da variável (se o **CODE_TYPE** for **CONST**) ou o bloco de in
  1. CHART_ORDER: INT que irá armazenar a posição padrão do grafico dentro do dashboard, sendo o valor 1 para o topo da exibição, 2 para a segunda posição e assim por diante, o campo tem inicialmente o valor padrão de 999 para que gráficos cujo a ordenação não for configurada sejam exibidos após os graficos com ordenação configurada.

IMPORTANTE Quando o CODE_TYPE for PHP é necessário que o código esteja tratado (escaped), uma vez que se trata de um bloco JSON. Para tanto, existem ferramentas online, como freeformatter.com. Tomando o exemplo acima, temos o seguinte código para a série categories: {.is-danger}

$user = \core\xDS::getUser();
$empresa = $user->EMPRESA_ATUAL;
$local = $user->LOCAL_ATUAL;
$filial = $user->FILIAL_ATUAL;
$timezone = $this->user->TIMEZONE ?? date_default_timezone_get();

$dIni = (empty($inicio)) ? new DateTime('-2 months', new DateTimeZone($timezone)) : DateTime::createFromFormat('d\/m\/Y', $inicio);
$dFim = (empty($fim)) ? new DateTime('now', new DateTimeZone($timezone)) : DateTime::createFromFormat('d\/m\/Y', $fim);
$comeco = date_format($dIni,'Y-m-d');
$final = date_format($dFim,'Y-m-d');

if (!empty($modelo)) {
	$modelos = (is_array($modelo)) ? ' and m.SEQ_DB in (' . implode(',', $modelo) . ') ' : " and m.SEQ_DB = {$modelo} ";
} else {
	$modelos = '';
}

$sql = "SELECT
	distinct m.DESCRICAO Modelo
	FROM app_apontamento_maquina a
	INNER JOIN app_equipamento e ON a.EQUIPAMENTO_SEQ_DB = e.SEQ_DB
	INNER JOIN app_modelo m ON e.MODELO_SEQ_DB = m.SEQ_DB
	WHERE DATE(a.INI_DH) between '{$comeco}' and '{$final}'
	and a.empresa = {$empresa} and a.local in ({$local},9999) and a.filial in ({$filial},9999) and a.ativo = 1 and a.deleted = 0
	{$modelos} GROUP BY m.SEQ_DB;";

$todosModelos = \core\ConexaoDB::query($sql, $params = []);
$series = [];
foreach ($todosModelos as $model){
	array_push($series,$model['Modelo']);
}
return $series;

Gráficos

Vídeos para ajuda: Criando gráficos para dashboard no NFS

Parte 1 de 5

Parte 2 de 5

Parte 3 de 5

Parte 4 de 5

Parte 5 de 5

Configuração de Reload No Dashboard

A configuração abaixo deve ser inserida quando o objetivo é atualizar os dados de todos os gráficos da tela ao mesmo tempo.

Obs: O tempo de reload a ser devinido está em segundos, logo se for a cada 1 minuto o valor deve ser 60 (segundos). O valor deve ser maior ou igual a 60 (1 minuto), caso contrário não será considerado

A configuração deve ser inserida no campo OPTIONS da tabela nfs_generic_dashboard

Exemplo: Reload a cada 1 minutos

{
  "reloadTime": 60
}

Configuração de Reload individual por gráfico/relatorio

Aviso! Se for adicionado o reloadTime individual em algum gráfico ou relatório, o reload configurado na tabela nfs_generic_dashboard será ignorado {.is-warning}

Para configurar o reload automático individual em um gráfico, basta adicionar no campo OPTIONS da tabela nfs_generic_chart o item reloadTime, conforme o exemplo abaixo:

Exemplo: Reload a cada 3 minutos

{
  "reloadTime": 180
}

Obs: O tempo de reload deve ser inserido em segundos!

Exibir Relatório no Dashboard

Para exibir um relatório na tela de Dashboard deve ser inserido no campo OPTIONS da tabela nfs_generic_chart o nome do relatório, que pode ser consultado na tabela nfs_reports no campo NAME.

No exemplo abaixo temos um relatório com o NAME = planejamento_diario_trato_madeira, logo, deve ser inserido no item report da configuração, como no exemplo abaixo:

{
  "report": "planejamento_diario_trato_madeira"
}

Para adicionar o reload automático no relatório basta adicionar à configuração o item reloadTime, como no exemplo a seguir:

{
  "report": "planejamento_diario_trato_madeira", 
  "reloadTime": 180
}

Obs: O tempo de reload deve ser inserido em segundos!

Aviso! Se for adicionado o reloadTime individual em algum gráfico ou relatório, o reload configurado na tabela nfs_generic_dashboard será ignorado {.is-warning}

Gerar imagem PNG de um gráfico

É possível gerar uma imagem PNG de um gráfico configurado na tabela nfs_generic_chart ou passar diretamente uma configuração de um gráfico no modelo Highcharts (modelos: https://www.highcharts.com/demo)

Exemplo:

 
  //['chartConfig'] -> enviando uma configuração highcarts diretamente 
  
  $args['chartConfig'] = '{"title": {"text": "Steep Chart"}, "xAxis": {"categories": ["Jan", "Feb", "Mar"]}, "series": [{"data": [29.9, 71.5, 106.4]}]}'; 
  
  //ou 
  
  //['seqDb'] -> pegar uma configuração da tabela nfs_generic_chart 
  $seqdb = 161; //seqdb da tabela nfs_generic_chart.
  $args['seqDb'] = $seqdb;

  $imgPng = ImageService::getHighchartsImage($args);

  echo '<img src="'.$imgPng.'">';

Inserir a imagem de um grafico em um relatorio

Para inserir a imagem de um gráfico no relatório basta definir como no exemplo acima se o gráfico virá de uma configuração ja feita na tabela nfs_generic_chart (SEQ_DB) ou se será gerada manualmente (ChartConfig).

Passo 01 - Inserir o código no entrypoint e enviar através do inputValues como no exemplo abaixo:

Imagem gerada a partir de uma configuração pronta na tabela nfs_generic_chart:


//['seqDb'] -> pegar uma configuração da tabela nfs_generic_chart 
  $seqDb = 161; //seqdb da tabela nfs_generic_chart.
  $args['seqDb'] = $seqDb;

  $imgPng = ImageService::getHighchartsImage($args);

  if (empty($img)){
    //salva no logs o erro, caso ocorra falha
    NFSLogger::error('Falha ao tentar pegar imagem de um grafico da tabela nfs_generic_chart, seqDB = '.$seqDb, 'EntryPoint', $imgPng);
  } else {
    //envia o base64 gerado para o relatório na variavel chartBase64
    $this->queryData['chartBase64'] = $imgPng;
  }
  

ou

Imagem gerada a partir de uma configuração feita manualmente

 
  //['chartConfig'] -> enviando uma configuração highcarts diretamente 
  
  $args['chartConfig'] = '{"title": {"text": "Steep Chart"}, "xAxis": {"categories": ["Jan", "Feb", "Mar"]}, "series": [{"data": [29.9, 71.5, 106.4]}]}'; 

  $imgPng = ImageService::getHighchartsImage($args);

  if (empty($img)){
    //salva no logs o erro, caso ocorra falha
    NFSLogger::error('Falha ao tentar pegar imagem de um grafico', 'EntryPoint', 'erro ao obter imagem do gráfico');
  } else {
    //envia o base64 gerado para o relatório na variavel chartBase64
    $this->queryData['chartBase64'] = $imgPng;
  }
  

Gerar imagem de um grafico no MOBILE

Exemplo de configuração do gráfico:

<body>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.7/css/all.css">

  <div>
    <table>
      <tr>
        <td>
          <figure class="highcharts-figure">
            <div id="tmo" class="chart-container"></div>
          </figure>
        </td>
    </table>
  </div>
  <script>
    var gaugeOptions = {
      chart: {
        type: 'gauge',
        plotBackgroundColor: null,
        plotBackgroundImage: null,
        plotBorderWidth: 0,
        plotShadow: false
        },
      title: null,
      pane: {
        startAngle: -100,
        endAngle: 100,
        background: [{
          backgroundColor: {
          linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
          stops: [
            [0, '#FFF'],
            [1, '#333']
          ]
          },
          borderWidth: 0,
          outerRadius: '109%'
        }, {
          backgroundColor: {
          linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
          stops: [
            [0, '#333'],
            [1, '#FFF']
          ]
          },
          borderWidth: 1,
          outerRadius: '107%'
        }, {
          // default background
        }, {
          backgroundColor: '#DDD',
          borderWidth: 0,
          outerRadius: '105%',
          innerRadius: '103%'
        }]
        },
      exporting: {
        enabled: false
      },
      tooltip: {
        enabled: false
      },
      // the value axis
      yAxis: {
        minorTickInterval: 'auto',
        minorTickWidth: 1,
        minorTickLength: 10,
        minorTickPosition: 'inside',
        minorTickColor: '#666',

        tickPixelInterval: 30,
        tickWidth: 2,
        tickPosition: 'inside',
        tickLength: 10,
        tickColor: '#666',
        labels: {
          step: 2,
          rotation: 'auto'
        },
      },
      credits: {
        enabled: false
      }
    };

  var columnBasic = Highcharts.chart('tmo', Highcharts.merge(gaugeOptions, {
    chart: {
      type: 'column'
    },
    xAxis: {
      categories: [
        'TMO'
      ],
      crosshair: true
    },
    yAxis: {
      min: 0,
      title: {
        text: 'Tempo (segundos)'
      }
    },
    tooltip: {
      headerFormat: '<span style="font-size:10px">{point.key}</span><table>',
      pointFormat: '<tr><td style="color:{series.color};padding:0">{series.name}: </td>' +
        '<td style="padding:0"><b>{point.y:.1f} mm</b></td></tr>',
      footerFormat: '</table>',
      shared: true,
      useHTML: true
    },
    plotOptions: {
      column: {
        pointPadding: 0.2,
        borderWidth: 0
      }
    },
    series: []
  }));

  function loadData() {
    xMovaJsApi.getData('onDataReceived');
    }

  function onDataReceived(result) {
    if (result) {
      //xMovaJsApi.toast(result);
      result = JSON.parse(result);
      if (columnBasic) {


        /* //OK - MAS OS REGISTROS PRECISAM EXISTIR EM SERIES PARA SEREM ATUALIZADOS
        let tempo_padrao = result[0].value;
        let tempo_apontado = result[1].value;

        let point = columnBasic.series[0].points[0];
        point.update(parseFloat(tempo_padrao));

        let point2 = columnBasic.series[1].points[0];
        point2.update(parseFloat(tempo_apontado));
        */


        /* //OK - MAS NÃO ATUALIZA O NAME
        var count = 0;
        result.forEach(function(element) {
          let data = element.value;
          let name = element.desc;

          let point = columnBasic.series[count].points[0];
          point.update(parseFloat(data));

          count ++;
        });
        */

        //LIMPAR DADOS DA SERIES
        var series_ids = [];
        for (var s in columnBasic.series) {
          series_ids.push(columnBasic.series[s].options.id);
        }
        for (var id in series_ids) {
          columnBasic.get(id).remove(false);
        }

        //ADICIONAR DADOS NA SERIES
        result.forEach(function (item) {
          columnBasic.addSeries({                                        
            name: item.desc,
            data: [parseFloat(item.value)]
          }, false);

        });
        columnBasic.redraw();

      }
    }

    setTimeout(function () {
      loadData();
    }, 10000);
  }
  loadData();
  </script>
</body>