i was wondering how can i show the tooltip on a piechart in Google Charts when the cursor hovers over the legends. The hover already activates a visual selection of the slice, but the tooltip isnt activated. When hovering over the slice, it works normally. visual selection of the slice on hover Here is my code: html <!---Pie charts ----> <div class="br-card py-3 mt-4 text-center"> <div class="d-flex flex-row justify-content-center"> <div style="flex:1;"> <google-chart [type]="pieChartImoveis.type" [data]="pieChartImoveis.data" [options]="pieChartImoveis.options" [width]="chartWidth/2" [height]="300" (select)="onPieSelect($event, 'imoveis')" (ready)="onChartReady($event, 'imoveis')"> </google-chart> </div> <div style="flex:1;"> <google-chart [type]="pieChartArea.type" [data]="pieChartArea.data" [options]="pieChartArea.options" [width]="chartWidth/2" [height]="300" (select)="onPieSelect($event, 'area')" (ready)="onChartReady($event, 'area')"> </google-chart> </div> </div> </div> typescript declare const google: any; import { Component, ViewChild, ViewChildren, ElementRef, QueryList, AfterViewInit, HostListener, ChangeDetectorRef } from '@angular/core'; import { GoogleChart, ChartType, ChartSelectionChangedEvent } from 'angular-google-charts'; import { MapaComponent } from 'src/app/components/mapa/mapa.component'; import { jsPDF } from 'jspdf'; import autoTable from 'jspdf-autotable'; @Component({ selector: 'app-terras-indigenas', templateUrl: './terras-indigenas.component.html', styleUrl: './terras-indigenas.component.css', standalone: true, imports: [GoogleChart, MapaComponent] }) export class TerrasIndigenasPageComponent implements AfterViewInit { public ChartType = ChartType; @ViewChild('graficoContainer', { read: ElementRef }) graficoContainer!: ElementRef; @ViewChild(MapaComponent) mapa!: MapaComponent; @ViewChildren(GoogleChart) charts!: QueryList<GoogleChart>; chartWidth = 100; selectedPorte: Porte | null = null; pieChartImoveis = { type: ChartType.PieChart, data: [ ['Pequeno', 87], ['Grande', 22], ['Médio', 10] ], options: { title: 'Tamanho do imóvel: Imóveis com sobreposição em TI', fontName: 'Raleway', fontSize: 14, titleTextStyle: { fontSize: 16, bold: true }, colors: ['#ffcd07', '#155bcb', '#168821'], backgroundColor: 'transparent', legend: { position: 'right', alignment: 'top' }, pieSliceText: 'percentage', pieSliceTextStyle: { color: 'black', fontSize: 14, bold: true }, pieHole: 0.3, is3D: false, tooltip: { trigger: 'both' } } }; pieChartArea = { type: ChartType.PieChart, title: 'Tamanho do imóvel: Área com sobreposição em TI', data: [ ['Pequeno', 88168.761], ['Grande', 3275.394], ['Médio', 266.482] ], options: { title: 'Tamanho do imóvel: Área com sobreposição em TI', fontName: 'Raleway', fontSize: 14, titleTextStyle: { fontSize: 16, bold: true }, colors: ['#ffcd07', '#155bcb', '#168821'], backgroundColor: 'transparent', legend: { position: 'right', alignment: 'top' }, pieSliceText: 'percentage', pieSliceTextStyle: { color: 'black', fontSize: 14, bold: true }, pieHole: 0.3, is3D: false, tooltip: { trigger: 'both' } } }; constructor(private cdr: ChangeDetectorRef) { } @HostListener('window:resize', ['$event']) onResize(event?: Event) { this.setChartWidth(); } ngAfterViewInit(): void { this.setChartWidth(); this.cdr.detectChanges(); } setChartWidth(): void { if (this.graficoContainer?.nativeElement) { this.chartWidth = this.graficoContainer.nativeElement.offsetWidth; } } onPieSelect(event: ChartSelectionChangedEvent | any, fonte: 'imoveis' | 'area') { const row = event?.selection?.[0]?.row ?? (typeof event?.row === 'number' ? event.row : null); if (row == null) return; const data = fonte === 'imoveis' ? this.pieChartImoveis.data : this.pieChartArea.data; const label = (data[row]?.[0] as Porte) || null; if (!label) return; // toggle - se clicar no mesmo porte, limpa o filtro this.selectedPorte = this.selectedPorte === label ? null : label; this.applyPorteFilter(); } clearPorteFilter() { this.selectedPorte = null; this.applyPorteFilter(); } private applyPorteFilter() { // atualiza a metrica de tamanho + dados const novaDataCombo = this.selectedPorte ? this.comboByPorte[this.selectedPorte] : this.comboAll; const novaDataFaixa = this.selectedPorte ? this.faixaByPorte[this.selectedPorte] : this.faixaAll; const novaDataComboTiSobre = this.selectedPorte ? this.comboTiSobreByPorte[this.selectedPorte] : this.comboTiSobreAll; //cria novo array this.imoveisSobreTiChart = { ...this.imoveisSobreTiChart, data: [...novaDataCombo] }; this.faixaSobreTiChart = { ...this.faixaSobreTiChart, data: [...novaDataFaixa] }; this.tiSobreposicaoFunaiChart = { ...this.tiSobreposicaoFunaiChart, data: [...novaDataComboTiSobre] }; // aplica destaque para a fatia escolhida const applySliceHighlight = (pie: any) => { const idx = pie.data.findIndex((r: any[]) => r[0] === this.selectedPorte); const slices: any = {}; pie.data.forEach((r: any[], i: number) => { slices = i === idx && this.selectedPorte != null ? { offset: 0.08 } : {}; }); pie.options = { ...pie.options, slices }; }; applySliceHighlight(this.pieChartImoveis); applySliceHighlight(this.pieChartArea); } exportarTabelaCsv() { const colunas = this.tabelaDeSintese.columnNames; const linhas = this.tabelaDeSintese.data; let csvContent = colunas.join(";") + "\n"; linhas.forEach(row => { csvContent += row.join(";") + "\n"; }); const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", "tabela_sintese.csv"); link.style.visibility = "hidden"; document.body.appendChild(link); link.click(); document.body.removeChild(link); } private async fitImage( dataUrl: string, maxWmm: number, maxHmm: number, pdf: jsPDF ): Promise<{ w: number; h: number }> { const img = new Image(); img.src = dataUrl; await img.decode(); const pxToMm = (px: number) => px * 25.4 / 96; // 96dpi const iw = pxToMm(img.width), ih = pxToMm(img.height); const ratio = Math.min(maxWmm / iw, maxHmm / ih); return { w: iw * ratio, h: ih * ratio }; } async criarPdf(): Promise<void> { const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); const pageW = pdf.internal.pageSize.getWidth(); const pageH = pdf.internal.pageSize.getHeight(); const margin = 10; let y = margin; pdf.setFontSize(16); pdf.text('Imóveis x Terras Indígenas — Relatório', margin, y); y += 8; const mapUrl = await this.mapa.capturePng(); const mapSize = await this.fitImage(mapUrl, pageW - 2 * margin, 90, pdf); pdf.addImage(mapUrl, 'PNG', margin, y, mapSize.w, mapSize.h); y += mapSize.h + 6; const chartUris: string[] = []; this.charts.forEach((c) => { try { const chart = (c as any).wrapper?.getChart?.(); if (chart?.getImageURI) chartUris.push(chart.getImageURI()); } catch { } }); const gap = 5; const colW = (pageW - 2 * margin - gap) / 2; const targetH = 70; for (let i = 0; i < chartUris.length; i++) { const isLeft = i % 2 === 0; if (isLeft && (y + targetH > pageH - 50)) { pdf.addPage(); y = margin; } const x = isLeft ? margin : margin + colW + gap; pdf.addImage(chartUris, 'PNG', x, y, colW, targetH); if (!isLeft) y += targetH + 6; } if (chartUris.length % 2 === 1) { y += targetH + 6; } if (y + 30 > pageH - 20) { pdf.addPage(); y = margin; } pdf.setFontSize(14); pdf.text(this.tabelaDeSintese.options.title, margin, y); y += 6; autoTable(pdf, { startY: y, head: [this.tabelaDeSintese.columnNames], body: this.tabelaDeSintese.data, styles: { fontSize: 9, cellPadding: 2 }, headStyles: { fillColor: [21, 91, 203], textColor: 255, halign: 'center' }, theme: 'grid', margin: { left: margin, right: margin } }); pdf.save('mapa-painel.pdf'); } onChartReady(event: any, fonte: 'imoveis' | 'area') { const chartWrapper = event.chartWrapper; if (!chartWrapper) return; const chart = chartWrapper.getChart(); if (!chart) return; google.visualization.events.removeAllListeners(chart); google.visualization.events.addListener(chart, 'onmouseover', (e: any) => { chart.setSelection([{ row: e.row }]); }); google.visualization.events.addListener(chart, 'onmouseout', () => { chart.setSelection([]); }); google.visualization.events.addListener(chartWrapper, 'select', () => { const sel = chartWrapper.getChart().getSelection(); if (sel.length) { const valorSelecionado = fonte === 'imoveis' ? this.pieChartImoveis.data[sel[0].row][0] : this.pieChartArea.data[sel[0].row][0]; this.selectedPorte = valorSelecionado as Porte; this.applyPorteFilter(); } }); } } Continue reading...