Componente de gráficos 3D reutilizável para Angular usando Three.js. Fiz esse projeto pra facilitar a criação de visualizações interativas sem precisar mexer diretamente com WebGL.
- 4 tipos de gráfico: Barras, Linhas, Pizza e Rosca (donut)
- Modo 2D e 3D: Alterna entre visualização plana e tridimensional
- Controles interativos: Rotaciona, move e dá zoom com o mouse
- Tooltips no hover: Mostra os dados quando passa o mouse
- Customizável: Muda cores, tamanhos e estilos
- Liga/desliga séries: Clica na legenda pra esconder ou mostrar séries
- Exporta PNG: Baixa o gráfico como imagem
- Responsivo: Se adapta ao tamanho do container
- TypeScript: Tudo tipado certinho
npm install three@^0.158.0
npm install --save-dev @types/three@^0.158.0Pega a pasta chart-3d-threejs e joga no seu projeto Angular:
src/app/components/
└── chart-3d-threejs/
├── chart-3d-threejs.component.ts
├── chart-3d-threejs.component.html
├── chart-3d-threejs.component.css
└── charts/
├── base-chart.ts
├── bar-chart.ts
├── line-chart.ts
└── pie-chart.ts
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Chart3DThreejsComponent } from './components/chart-3d-threejs/chart-3d-threejs.component';
@NgModule({
declarations: [
Chart3DThreejsComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule // importante pra animações funcionarem
]
})
export class AppModule { }import { Component } from '@angular/core';
import { Chart3DConfig } from './components/chart-3d-threejs/chart-3d-threejs.component';
@Component({
selector: 'app-root',
template: '<app-chart-3d-threejs [chartData]="chartData"></app-chart-3d-threejs>'
})
export class AppComponent {
chartData: Chart3DConfig = {
title: 'Vendas Trimestrais',
type: 'bar',
mode: '3d',
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Receita', values: [120, 145, 138, 160] },
{ name: 'Custos', values: [80, 95, 88, 100] }
]
};
}Pronto! Já vai aparecer um gráfico de barras 3D interativo.
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Chart3DConfig } from './components/chart-3d-threejs/chart-3d-threejs.component';
@Component({
selector: 'app-dashboard',
template: '<app-chart-3d-threejs [chartData]="chartData"></app-chart-3d-threejs>'
})
export class DashboardComponent implements OnInit {
chartData!: Chart3DConfig;
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get<any[]>('/api/vendas-mensais').subscribe(dados => {
this.chartData = {
title: 'Desempenho Mensal',
type: 'line',
mode: '3d',
labels: dados.map(d => d.mes),
series: [
{ name: 'Vendas', values: dados.map(d => d.total) }
]
};
});
}
}chartData: Chart3DConfig = {
title: 'Vendas por Região',
type: 'bar',
labels: ['Norte', 'Sul', 'Leste', 'Oeste'],
series: [
{ name: '2023', values: [100, 120, 90, 110] },
{ name: '2024', values: [120, 140, 110, 130] }
]
};chartData: Chart3DConfig = {
title: 'Temperatura ao longo do ano',
type: 'line',
labels: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun'],
series: [
{ name: 'São Paulo', values: [25, 26, 24, 22, 20, 18] },
{ name: 'Rio', values: [30, 31, 29, 27, 25, 23] }
]
};chartData: Chart3DConfig = {
title: 'Participação de Mercado',
type: 'pie',
labels: ['Produto A', 'Produto B', 'Produto C'],
series: [
{ name: 'Market Share', values: [450, 320, 230] }
]
};chartData: Chart3DConfig = {
title: 'Alocação de Orçamento',
type: 'donut',
labels: ['Marketing', 'Desenvolvimento', 'Operações'],
series: [
{ name: 'Orçamento', values: [35000, 50000, 25000] }
]
};| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
title |
string? |
- | Título do gráfico |
labels |
string[] |
obrigatório | Rótulos do eixo X |
series |
ChartDataSeries[] |
obrigatório | Array com as séries de dados |
type |
'bar' | 'line' | 'pie' | 'donut' |
'bar' |
Tipo do gráfico |
mode |
'2d' | '3d' |
'3d' |
Modo de visualização |
maxHeight |
number |
8 |
Altura máxima das barras/linhas |
barWidth |
number |
automático | Largura das barras |
barDepth |
number |
automático | Profundidade das barras (3D) |
spacing |
number |
automático | Espaçamento entre barras |
showInstructions |
boolean |
true |
Mostra dica de ajuda ao carregar |
backgroundColor |
string |
'#ffffff' |
Cor de fundo |
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
name |
string |
obrigatório | Nome da série |
values |
number[] |
obrigatório | Valores dos dados |
color |
string? |
automático | Cor em hexadecimal (ex: '#3b82f6') |
chartData: Chart3DConfig = {
title: 'Com cores customizadas',
type: 'bar',
labels: ['A', 'B', 'C'],
series: [
{ name: 'Série 1', values: [10, 20, 15], color: '#ef4444' }, // vermelho
{ name: 'Série 2', values: [15, 25, 20], color: '#10b981' }, // verde
{ name: 'Série 3', values: [8, 18, 12] } // cor automática
]
};Quando você não quer que o usuário fique girando o gráfico (tipo num dashboard estático), usa o modo 2D:
chartData: Chart3DConfig = {
title: 'Vista fixa',
type: 'bar',
mode: '2d', // desabilita rotação e mostra vista frontal
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Vendas', values: [100, 120, 110, 140] }
]
};export class MeuComponente {
chartData: Chart3DConfig = { /* config inicial */ };
atualizarDados(novosDados: number[]) {
// cria um novo objeto pra triggar o change detection do Angular
this.chartData = {
...this.chartData,
series: [
{ name: 'Atualizado', values: novosDados }
]
};
}
}- Limite de pontos: Tenta manter menos de 50 pontos por série pra não travar
- Usa modo 2D: Se tiver vários gráficos na tela, o modo 2D é mais leve
- Desliga as instruções: Em produção, coloca
showInstructions: false - Fundo transparente: Se não precisa de fundo, ajuda na performance
Organizei tudo de forma modular pra facilitar manutenção:
chart-3d-threejs/
├── chart-3d-threejs.component.ts # Componente principal
├── chart-3d-threejs.component.html # Template com toolbar
├── chart-3d-threejs.component.css # Estilos
└── charts/
├── base-chart.ts # Classe base abstrata
├── bar-chart.ts # Lógica do gráfico de barras
├── line-chart.ts # Lógica do gráfico de linhas
└── pie-chart.ts # Lógica dos gráficos pizza/donut
Se quiser adicionar um novo tipo de gráfico, é só estender a classe BaseChart:
import { BaseChart } from './base-chart';
import * as THREE from 'three';
export class MeuGrafico extends BaseChart {
create(): void {
// sua lógica de renderização aqui
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x3b82f6 });
const mesh = new THREE.Mesh(geometry, material);
this.scene.add(mesh);
}
}Depois adiciona no switch do componente principal e pronto!
- Angular 16: Framework
- Three.js 0.158: Engine de renderização 3D
- TypeScript 5.1: Linguagem
- OrbitControls: Controle de câmera
- WebGL: Aceleração por GPU
- Verifica se importou o
BrowserAnimationsModule - Olha o console do navegador se tem erro do Three.js
- Confere se o container tem altura definida (não pode ser 0)
- Diminui o
maxHeight - Usa menos pontos de dados
- Muda pra
mode: '2d' - Reduz o número de séries
- Usa formato hex nas cores (ex:
'#3b82f6') - Verifica se os valores não são todos zero
- Confere se a série tá visível na legenda
Funciona em todos os navegadores modernos:
- Chrome (recomendado)
- Firefox
- Safari
- Edge
- Navegadores mobile (com suporte a touch)
MIT - usa à vontade em projetos comerciais e pessoais.
Pull requests são bem-vindos! Se encontrar algum bug ou tiver sugestões, abre uma issue.
Desenvolvido com Angular e Three.js